package com.example.security.oro;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
import java.util.stream.Collectors;

import hk.oro.iefas.ws.mail.service.MailService;
import hk.oro.iefas.core.util.*;
import hk.oro.iefas.domain.organization.dto.PostDTO;
import hk.oro.iefas.domain.organization.entity.Post;
import hk.oro.iefas.domain.shroff.dto.*;
import hk.oro.iefas.domain.shroff.entity.*;
import hk.oro.iefas.domain.system.entity.SysAttachment;
import hk.oro.iefas.ws.organization.service.PostService;
import hk.oro.iefas.ws.shroff.repository.*;
import hk.oro.iefas.ws.shroff.repository.assembler.*;
import hk.oro.iefas.ws.shroff.service.*;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import hk.oro.iefas.core.constant.CoreConstant;
import hk.oro.iefas.core.constant.FundsConstant;
import hk.oro.iefas.core.constant.MsgCodeConstant;
import hk.oro.iefas.core.constant.MsgParamCodeConstant;
import hk.oro.iefas.core.constant.PrivilegeConstant;
import hk.oro.iefas.core.constant.ShroffConstant;
import hk.oro.iefas.core.constant.WorkFlowAction;
import hk.oro.iefas.core.exceptions.BusinessException;
import hk.oro.iefas.core.exceptions.support.ErrorMsg;
import hk.oro.iefas.core.security.UserInfo;
import hk.oro.iefas.core.util.BusinessUtils;
import hk.oro.iefas.core.util.CommonUtils;
import hk.oro.iefas.core.util.DataUtils;
import hk.oro.iefas.core.util.DateUtils;
import hk.oro.iefas.core.util.MimeTypeUtils;
import hk.oro.iefas.core.util.SecurityUtils;
import hk.oro.iefas.domain.bank.dto.CurrencyDTO;
import hk.oro.iefas.domain.casemgt.dto.CaseAccountInfoDTO;
import hk.oro.iefas.domain.casemgt.dto.CaseAccountReverseMoneyDTO;
import hk.oro.iefas.domain.casemgt.dto.CaseDTO;
import hk.oro.iefas.domain.casemgt.entity.Case;
import hk.oro.iefas.domain.casemgt.entity.CaseAccountInfo;
import hk.oro.iefas.domain.casemgt.entity.CaseAccountReverseMoney;
import hk.oro.iefas.domain.common.dto.ActionDTO;
import hk.oro.iefas.domain.common.dto.ApplicationCodeTableDTO;
import hk.oro.iefas.domain.dividend.dto.SysApprovalWfDTO;
import hk.oro.iefas.domain.dividend.entity.CaseFee;
import hk.oro.iefas.domain.dividend.entity.CaseFeeMain;
import hk.oro.iefas.domain.report.DownloadFileDTO;
import hk.oro.iefas.domain.search.dto.SearchDTO;
import hk.oro.iefas.domain.shroff.dto.AnalysisCodeDTO;
import hk.oro.iefas.domain.shroff.dto.BankTransferItemDTO;
import hk.oro.iefas.domain.shroff.dto.ChequeDTO;
import hk.oro.iefas.domain.shroff.dto.CommonPayeeDTO;
import hk.oro.iefas.domain.shroff.dto.ControlAccountDTO;
import hk.oro.iefas.domain.shroff.dto.JournalVoucherAccountItemDTO;
import hk.oro.iefas.domain.shroff.dto.JournalVoucherBasicInformationDTO;
import hk.oro.iefas.domain.shroff.dto.JournalVoucherDetailDTO;
import hk.oro.iefas.domain.shroff.dto.JournalVoucherResultItemDTO;
import hk.oro.iefas.domain.shroff.dto.JournalVoucherSearchDTO;
import hk.oro.iefas.domain.shroff.dto.PaymentVoucherAccountItemDTO;
import hk.oro.iefas.domain.shroff.dto.PaymentVoucherBasicInformationDTO;
import hk.oro.iefas.domain.shroff.dto.PaymentVoucherDetailDTO;
import hk.oro.iefas.domain.shroff.dto.PaymentVoucherResultDTO;
import hk.oro.iefas.domain.shroff.dto.PaymentVoucherSearchDTO;
import hk.oro.iefas.domain.shroff.dto.PendingMoneyDTO;
import hk.oro.iefas.domain.shroff.dto.ReceiptDTO;
import hk.oro.iefas.domain.shroff.dto.ReceiptVoucherAccountItemDTO;
import hk.oro.iefas.domain.shroff.dto.ReceiptVoucherBasicInfoDTO;
import hk.oro.iefas.domain.shroff.dto.ReceiptVoucherDetailDTO;
import hk.oro.iefas.domain.shroff.dto.ReceiptVoucherResultDTO;
import hk.oro.iefas.domain.shroff.dto.ReceiptVoucherSearchDTO;
import hk.oro.iefas.domain.shroff.dto.VoucherDTO;
import hk.oro.iefas.domain.shroff.dto.VoucherDetailDTO;
import hk.oro.iefas.domain.shroff.dto.VoucherItemDTO;
import hk.oro.iefas.domain.shroff.entity.AnalysisCode;
import hk.oro.iefas.domain.shroff.entity.ShrAutoPayItem;
import hk.oro.iefas.domain.shroff.entity.ShrCheque;
import hk.oro.iefas.domain.shroff.entity.ShrComPayeeMain;
import hk.oro.iefas.domain.shroff.entity.ShrCtlAc;
import hk.oro.iefas.domain.shroff.entity.ShrPendingMoney;
import hk.oro.iefas.domain.shroff.entity.ShrReceipt;
import hk.oro.iefas.domain.shroff.entity.ShrVcrItem;
import hk.oro.iefas.domain.shroff.entity.Voucher;
import hk.oro.iefas.domain.shroff.entity.VoucherType;
import hk.oro.iefas.domain.system.dto.ApplParamDTO;
import hk.oro.iefas.domain.system.dto.SysWorkFlowRuleDTO;
import hk.oro.iefas.domain.system.entity.SysApprovalWf;
import hk.oro.iefas.ws.bank.service.CurrencyService;
import hk.oro.iefas.ws.casemgt.repository.CaseAccountInfoRepository;
import hk.oro.iefas.ws.casemgt.repository.CaseAccountReserveMoneyServiceRepository;
import hk.oro.iefas.ws.casemgt.repository.predicate.CaseAccountInfoPredicate;
import hk.oro.iefas.ws.casemgt.service.CaseAccountService;
import hk.oro.iefas.ws.common.service.CommonService;
import hk.oro.iefas.ws.common.util.RequestContextUtils;
import hk.oro.iefas.ws.core.constant.SysSequenceEnum;
import hk.oro.iefas.ws.dividend.repository.AnalysisCodeRepository;
import hk.oro.iefas.ws.dividend.repository.CaseFeeMainRepository;
import hk.oro.iefas.ws.dividend.repository.predicate.CaseFeeMainPredicate;
import hk.oro.iefas.ws.dividend.service.AnalysisCodeService;
import hk.oro.iefas.ws.shroff.repository.ChequeRepository;
import hk.oro.iefas.ws.shroff.repository.ComPayeeRepository;
import hk.oro.iefas.ws.shroff.repository.ControlAccountRepository;
import hk.oro.iefas.ws.shroff.repository.JournalTypeRepository;
import hk.oro.iefas.ws.shroff.repository.PendingMoneyRepository;
import hk.oro.iefas.ws.shroff.repository.ShrAutoPayItemRepository;
import hk.oro.iefas.ws.shroff.repository.ShrVcrItemRepository;
import hk.oro.iefas.ws.shroff.repository.VoucherRepository;
import hk.oro.iefas.ws.shroff.repository.VoucherTypeRepository;
import hk.oro.iefas.ws.shroff.repository.assembler.CommonPayeeDTOAssembler;
import hk.oro.iefas.ws.shroff.repository.assembler.JournalVoucherDetailDTOAssembler;
import hk.oro.iefas.ws.shroff.repository.assembler.JournalVoucherResultItemDTOAssembler;
import hk.oro.iefas.ws.shroff.repository.assembler.PaymentVoucherDetailDTOAssembler;
import hk.oro.iefas.ws.shroff.repository.assembler.PaymentVoucherResultDTOAssembler;
import hk.oro.iefas.ws.shroff.repository.assembler.ReceiptVoucherDetailDTOAssembler;
import hk.oro.iefas.ws.shroff.repository.assembler.ReceiptVoucherResultDTOAssembler;
import hk.oro.iefas.ws.shroff.repository.assembler.VoucherDetailDTOAssembler;
import hk.oro.iefas.ws.shroff.repository.predicate.ChequePredicate;
import hk.oro.iefas.ws.shroff.repository.predicate.VoucherPredicate;
import hk.oro.iefas.ws.shroff.service.ChequeService;
import hk.oro.iefas.ws.shroff.service.ControlAccountService;
import hk.oro.iefas.ws.shroff.service.ReceiptService;
import hk.oro.iefas.ws.shroff.service.ShrGeneralLedgerService;
import hk.oro.iefas.ws.shroff.service.VoucherService;
import hk.oro.iefas.ws.system.repository.SysApprovalWFRepository;
import hk.oro.iefas.ws.system.service.ApplParamService;
import hk.oro.iefas.ws.system.service.SysSequenceService;
import hk.oro.iefas.ws.system.service.SysWorkFlowRuleService;
import hk.oro.iefas.ws.system.service.SystemParameterService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ObjectUtils;

import javax.persistence.EntityManager;

import javax.mail.MessagingException;

/**
 * @author $Author: accidia.huang $
 * @version $Revision: 9606 $ $Date: 2019-04-17 17:53:18 +0800 (周三, 17 四月 2019) $
 */
@Slf4j
@Service
public class VoucherServiceImpl implements VoucherService {

    public static final String CQ = "CQ";
    private static final String TEMPLATE_FILE_NAME = "Import Account Template.csv";
    public static final String BTF = "BTF";

    @Autowired
    private VoucherRepository voucherRepository;

    @Autowired
    private ShrVcrItemRepository shrVcrItemRepository;

    @Autowired
    private VoucherTypeRepository voucherTypeRepository;

    @Autowired
    private CaseAccountInfoRepository caseAccountInfoRepository;

    @Autowired
    private ChequeRepository chequeRepository;

    @Autowired
    private ReceiptVoucherDetailDTOAssembler receiptVoucherDetailDTOAssembler;

    @Autowired
    private JournalVoucherDetailDTOAssembler journalVoucherDetailDTOAssembler;

    @Autowired
    private JournalVoucherResultItemDTOAssembler journalVoucherResultItemDTOAssembler;

    @Autowired
    private PaymentVoucherDetailDTOAssembler paymentVoucherDetailDTOAssembler;

    @Autowired
    private PaymentVoucherResultDTOAssembler paymentVoucherResultDTOAssembler;

    @Autowired
    private ReceiptVoucherResultDTOAssembler receiptVoucherResultDTOAssembler;

    @Autowired
    private CommonService commonService;

    @Autowired
    private ControlAccountRepository controlAccountRepository;

    @Autowired
    private CaseFeeMainRepository caseFeeMainRepository;

    @Autowired
    private SysApprovalWFRepository sysApprovalWFRepository;

    @Autowired
    private SysSequenceService sysSequenceService;

    @Autowired
    private ControlAccountService controlAccountService;

    @Autowired
    private ShrGeneralLedgerService shrGeneralLedgerService;

    @Autowired
    private ChequeService chequeService;

    @Autowired
    private CurrencyService currencyService;

    @Autowired
    private ComPayeeRepository comPayeeRepository;

    @Autowired
    private CommonPayeeDTOAssembler commonPayeeDTOAssembler;

    @Autowired
    private SysWorkFlowRuleService sysWorkFlowRuleService;

    @Autowired
    private SystemParameterService sysParamService;

    @Autowired
    private ReceiptService receiptService;

    @Autowired
    private VoucherDetailDTOAssembler voucherDetailDTOAssembler;

    @Autowired
    private JournalTypeRepository journalTypeRepository;

    @Autowired
    private ShrAutoPayItemRepository shrAutoPayItemRepository;

    private List<SysWorkFlowRuleDTO> receiptVoucherWorkFlowRuleList;

    private List<SysWorkFlowRuleDTO> paymentVoucherWorkFlowRuleList;

    private List<SysWorkFlowRuleDTO> journalVoucherWorkFlowRuleList;

    @Autowired
    private PendingMoneyRepository pendingMoneyRepository;

    @Autowired
    private CaseAccountReserveMoneyServiceRepository rserverMoneyServiceRepository;

    @Autowired
    private CaseAccountService caseAccountService;

    @Autowired
    private ApplParamService applParamService;

    @Autowired
    private AnalysisCodeService analysisCodeService;

    @Autowired
    private AnalysisCodeRepository analysisCodeRepository;

    @Autowired
    private MailService mailService;

    @Autowired
    private VoucherAttachmentRepository voucherAttachmentRepository;

    @Autowired
    private SysAttachmentRepository sysAttachmentRepository;

    @Autowired
    private SysAttachmentService sysAttachmentService;

    @Autowired
    private VoucherAttachmentService voucherAttachmentService;

    @Autowired
    private PostService postService;

    @Autowired
    private PaymentVoucherAccountItemDTOAssembler paymentVoucherAccountItemDTOAssembler;

    @Autowired
    VoucherService voucherService;

    @Autowired
    private EntityManager entityManager;

    private void initReceiptVoucherWorkFlowRuleList() {
        if (receiptVoucherWorkFlowRuleList == null) {
            receiptVoucherWorkFlowRuleList
                = sysWorkFlowRuleService.findByPrivilegeCode(PrivilegeConstant.PRIVILEGE_RVM);
        }
    }

    private void initPaymentVoucherWorkFlowRuleList() {
        if (paymentVoucherWorkFlowRuleList == null) {
            paymentVoucherWorkFlowRuleList
                = sysWorkFlowRuleService.findByPrivilegeCode(PrivilegeConstant.PRIVILEGE_PVM);
        }
    }

    private void initJournalVoucherWorkFlowRuleList() {
        if (journalVoucherWorkFlowRuleList == null) {
            journalVoucherWorkFlowRuleList
                = sysWorkFlowRuleService.findByPrivilegeCode(PrivilegeConstant.PRIVILEGE_JVM);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer saveReceiptVoucher(ReceiptVoucherBasicInfoDTO receiptVoucherBasicInformation) {
        log.info("saveReceiptVoucher() start - " + receiptVoucherBasicInformation);
        if (!receiptVoucherBasicInformation.getStatus().equals(WorkFlowAction.Delete.getCode())) {
            prepareReceiptVoucherWorkFlow(receiptVoucherBasicInformation, WorkFlowAction.Save.getCode());
        }
        VoucherType voucherType = voucherTypeRepository.findByVoucherTypeCode(ShroffConstant.VT_REC);
        ControlAccountDTO controlAccount = receiptVoucherBasicInformation.getControlAccount();
        if (controlAccount != null && controlAccount.getCtlAcId() != null && controlAccount.getCtlAcId() != 0) {
            receiptVoucherBasicInformation
                .setControlAccount(controlAccountService.getControlAccountDetail(controlAccount.getCtlAcId()));
        } else {
            receiptVoucherBasicInformation.setControlAccount(null);
        }
        Voucher voucher = DataUtils.copyProperties(receiptVoucherBasicInformation, Voucher.class);
        voucher.setVoucherType(voucherType);
        voucher = voucherRepository.save(voucher);
        Integer voucherId = voucher.getVoucherId();
        log.info("saveReceiptVoucher() end - voucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(readOnly = true)
    @Override
    public Page<ReceiptVoucherResultDTO> findReceiptVoucherByCriteria(SearchDTO<ReceiptVoucherSearchDTO> searchDTO) {
        log.info("findReceiptVoucherByCriteria() start - " + searchDTO);
        Page<ReceiptVoucherResultDTO> results = null;
        if (searchDTO != null) {
            Pageable pageable = null;
            if (searchDTO.getPage() != null) {
                pageable = searchDTO.getPage().toPageable();
            }
            if (searchDTO.getCriteria() != null) {
                Page<Voucher> page = this.voucherRepository
                    .findAll(VoucherPredicate.searchReceiptVoucher(searchDTO.getCriteria()), pageable);
                results = receiptVoucherResultDTOAssembler.toResultDTO(page);
            }
        }
        log.info("findReceiptVoucherByCriteria() end - " + results);
        return results;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer saveJournalVoucher(JournalVoucherDetailDTO journalVoucherDetail) {
        log.info("saveJournalVoucher() start - " + journalVoucherDetail);
        if (!journalVoucherDetail.getJournalVoucherBasicInformation().getStatus()
            .equals(WorkFlowAction.Delete.getCode())) {
            prepareJournalVoucherWorkFlow(journalVoucherDetail.getJournalVoucherBasicInformation(),
                WorkFlowAction.Save.getCode());
        }
        JournalVoucherBasicInformationDTO basicInformationDTO
            = journalVoucherDetail.getJournalVoucherBasicInformation();
        Long workflowId
            = saveWorkFlowHistory(basicInformationDTO.getWorkflowId(), basicInformationDTO.getSysWorkFlowRule(), null);
        basicInformationDTO.setWorkflowId(workflowId);
        Integer voucherId = saveJournalVoucherInside(journalVoucherDetail);
        log.info("saveJournalVoucher() end VoucherId: " + voucherId);
        return voucherId;
    }

    private Integer saveJournalVoucherInside(JournalVoucherDetailDTO journalVoucherDetail) {
        JournalVoucherBasicInformationDTO journalVoucherBasicInformation
            = journalVoucherDetail.getJournalVoucherBasicInformation();
        Voucher voucher = DataUtils.copyProperties(journalVoucherBasicInformation, Voucher.class);
        VoucherType voucherType = voucherTypeRepository.findByVoucherTypeCode(ShroffConstant.VT_JOU);
        voucher.setVoucherType(voucherType);
        voucher = voucherRepository.save(voucher);
        Integer voucherId = voucher.getVoucherId();

        List<JournalVoucherAccountItemDTO> journalVoucherAccountItems
            = journalVoucherDetail.getJournalVoucherAccountItems();
        if (CommonUtils.isNotEmpty(journalVoucherAccountItems)) {
            journalVoucherAccountItems.forEach(item -> {
                ShrVcrItem shrVcrItem = DataUtils.copyProperties(item, ShrVcrItem.class);
                shrVcrItem.setVoucherId(voucherId);
                if (item.getAccount() != null) {
                    CaseAccountInfo caseAccountInfo
                        = caseAccountInfoRepository.findOne(item.getAccount().getCaseAcId());
                    shrVcrItem.setCaseAccount(caseAccountInfo);
                }
                if (CoreConstant.STATUS_ACTIVE.equals(item.getStatus())) {
                    shrVcrItem.setNature(item.getParticulars());
                    if (item.getAmountDr() != null && BigDecimal.ZERO.compareTo(item.getAmountDr()) != 0) {
                        shrVcrItem.setVoucherAmount(item.getAmountDr());
                        shrVcrItem.setDebitCredit(ShroffConstant.DR);
                    } else {
                        shrVcrItem.setVoucherAmount(item.getAmountCr());
                        shrVcrItem.setDebitCredit(ShroffConstant.CR);
                    }
                }
                shrVcrItemRepository.save(shrVcrItem);

            });
        }
        
        VoucherDTO voucherDTO = journalVoucherDetail.getVoucherDTO();
        if (journalVoucherBasicInformation.getIsNewOrNot() != null && journalVoucherBasicInformation.getIsNewOrNot() && voucherDTO != null && voucherDTO.getVoucherId() != null) {
            Map<SysAttachmentDTO, VoucherAttachmentDTO> voucherAttachmentDTOs
                    = getVoucherAttachmentDTOs(voucherDTO.getVoucherId());
            if (voucherAttachmentDTOs != null && journalVoucherBasicInformation.getIsAutoPaymentVoucher()) {
                for (SysAttachmentDTO sysAttachmentDTO : voucherAttachmentDTOs.keySet()) {
                    Integer attachmentId = sysAttachmentService.saveSysAttachment(sysAttachmentDTO);
                    VoucherAttachmentDTO voucherAttachmentDTO = voucherAttachmentDTOs.get(sysAttachmentDTO);
                    voucherAttachmentDTO.setVoucherId(voucherId);
                    voucherAttachmentDTO.setAttachmentId(attachmentId);
                    voucherAttachmentService.saveVoucherAttachment(voucherAttachmentDTO);
                }
            }
        }
        return voucherId;
    }

    @Transactional(readOnly = true)
    @Override
    public JournalVoucherDetailDTO findJournalVoucher(Integer voucherId) {
        log.info("findJournalVoucher() start - VoucherId: " + voucherId);
        Voucher voucher = voucherRepository.findOne(voucherId);
        List<ShrVcrItem> shrVcrItems = shrVcrItemRepository.findAvailableVoucherItem(voucherId);
        List<SysApprovalWf> approvalWorkflows = sysApprovalWFRepository
            .findByWorkflowIdAndStatusIgnoreCaseOrderBySeqDesc(voucher.getWorkflowId(), CoreConstant.STATUS_ACTIVE);
        JournalVoucherDetailDTO dto = journalVoucherDetailDTOAssembler.toDTO(voucher, shrVcrItems, approvalWorkflows);
        ActionDTO actionDTO = sysWorkFlowRuleService.findAction(PrivilegeConstant.PRIVILEGE_JVM, voucher.getStatus());
        dto.setAction(actionDTO);
        log.info("findJournalVoucher() end - " + dto);
        return dto;
    }

    @Transactional(readOnly = true)
    @Override
    public Page<JournalVoucherResultItemDTO> searchJournalVoucher(SearchDTO<JournalVoucherSearchDTO> searchDTO) {
        log.info("searchJournalVoucher() start - " + searchDTO);

        Page<Voucher> entities = voucherRepository
            .findAll(VoucherPredicate.searchJournalVoucher(searchDTO.getCriteria()), searchDTO.getPage().toPageable());
        Page<JournalVoucherResultItemDTO> result = null;
        if (entities != null) {
            result = journalVoucherResultItemDTOAssembler.toResultDTO(entities);
        }
        log.info("searchJournalVoucher() end - " + result);
        return result;
    }

    @Transactional(readOnly = true)
    @Override
    public Page<PaymentVoucherResultDTO> findPaymentVoucherByCriteria(SearchDTO<PaymentVoucherSearchDTO> criteria) {
        log.info("findPaymentVoucherByCriteria() start - criteria = " + criteria);
        Page<Voucher> page = null;
        Page<PaymentVoucherResultDTO> result = null;
        if (criteria != null) {
            Pageable pageable = null;
            if (criteria.getPage() != null) {
                pageable = criteria.getPage().toPageable();
            }
            if (criteria.getCriteria() != null) {
                page = voucherRepository.findAll(VoucherPredicate.searchPaymentVoucher(criteria.getCriteria()),
                    pageable);
            }

            if (page != null) {
                result = paymentVoucherResultDTOAssembler.toResultDTO(page);
            }

        }
        log.info("findPaymentVoucherByCriteria() end - ");
        return result;
    }

    @Override
    public List<PaymentVoucherResultDTO> findPaymentVoucherListByCriteria(PaymentVoucherSearchDTO criteria) {
        List<PaymentVoucherResultDTO> result = new ArrayList<>();
        List<Voucher> respone = new ArrayList<>();
        if (criteria != null) {
            respone = (List<Voucher>)voucherRepository.findAll(VoucherPredicate.searchPaymentVoucher(criteria));
        }
        if (respone.size() > 0) {
            result = paymentVoucherResultDTOAssembler.toDTOList(respone);
            // result = DataUtils.copyPropertiesForListd(respone, PaymentVoucherResultDTO.class);
        }
        return result;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer saveReceiptVoucher(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        log.info("saveReceiptVoucher() start - " + receiptVoucherDetail);
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInformation
            = receiptVoucherDetail.getReceiptVoucherBasicInformation();
        if (!receiptVoucherBasicInformation.getStatus().equals(WorkFlowAction.Delete.getCode())) {
            prepareReceiptVoucherWorkFlow(receiptVoucherBasicInformation, WorkFlowAction.Save.getCode());
        }
        Long workflowId = saveWorkFlowHistory(receiptVoucherBasicInformation.getWorkflowId(),
            receiptVoucherBasicInformation.getSysWorkFlowRule(), null);
        receiptVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = saveReceiptVoucherInside(receiptVoucherDetail);

        log.info("saveReceiptVoucher() end - voucherId: " + voucherId);
        return voucherId;
    }

    private Integer saveReceiptVoucherInside(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo = receiptVoucherDetail.getReceiptVoucherBasicInformation();
        Voucher voucher = DataUtils.copyProperties(receiptVoucherBasicInfo, Voucher.class);
        VoucherType voucherType = voucherTypeRepository.findByVoucherTypeCode(ShroffConstant.VT_REC);
        if (voucher.getVoucherId()!=null){
            Voucher vcr = voucherRepository.findVoucherByVoucherId(voucher.getVoucherId());
            if (vcr!=null){
                voucher.setVersionNo(vcr.getVersionNo());
            }
        }
        
        voucher.setVoucherType(voucherType);

        if (receiptVoucherBasicInfo.getControlAccount() != null
            && receiptVoucherBasicInfo.getControlAccount().getCtlAcId() != null
            && receiptVoucherBasicInfo.getControlAccount().getCtlAcId() != 0) {
            ShrCtlAc controlAccount
                = controlAccountRepository.findOne(receiptVoucherBasicInfo.getControlAccount().getCtlAcId());
            // if (receiptVoucherBasicInfo.getControlAccount().getLiquidCashAmount() != null) {
            // controlAccount.setLiquidCashAmount(receiptVoucherBasicInfo.getControlAccount().getLiquidCashAmount());
            // }
            voucher.setControlAccount(controlAccount);
        } else {
            voucher.setControlAccount(null);
        }
        voucher = voucherRepository.save(voucher);
        Integer voucherId = voucher.getVoucherId();

        List<ReceiptVoucherAccountItemDTO> receiptVoucherAccountItems
            = receiptVoucherDetail.getReceiptVoucherAccountItems();
        Set<Integer> savedChequeId = new HashSet<>();
        Set<ShrCheque> chequesOfDeedToDeleteVoucherId = new HashSet<>();
        Set<ShrCheque> chequesOfNoDeedToDeleteVoucherId = new HashSet<>();
        if (CommonUtils.isNotEmpty(receiptVoucherAccountItems)) {
            receiptVoucherAccountItems.forEach(item -> {
                ShrVcrItem shrVcrItem = DataUtils.copyProperties(item, ShrVcrItem.class);
                ChequeDTO cheque = item.getCheque();
                if (cheque != null) {
                    if (cheque.getChequeId() != null) {
                        ShrCheque shrCheque = chequeRepository.findOne(cheque.getChequeId());
                        if (receiptVoucherBasicInfo.getStatus().equals(CoreConstant.VOUCHER_STATUS_DELETED)) {
                            if (!savedChequeId.contains(shrCheque.getChequeId())) {
                                shrCheque.setStatus(CoreConstant.CHEQUE_STATUS_PENDING);
                                shrCheque.setShrVcrInfo(null);
                                shrCheque = chequeRepository.save(shrCheque);
                                chequeService.syncReceiveDeposit(shrCheque.getChequeId());
                                savedChequeId.add(shrCheque.getChequeId());
                            }
                        }
                        shrVcrItem.setShrCheque(shrCheque);
                    }
                    shrVcrItem.setChequeNo(cheque.getChequeNo());
                    shrVcrItem.setChequeDate(cheque.getChequeDate());
                }
                ShrCheque shrCheque = shrVcrItem.getShrCheque();
                if (CoreConstant.STATUS_INACTIVE.equals(item.getStatus())
                    || CoreConstant.STATUS_DELETE.equals(item.getStatus())) {
                    shrVcrItem.setStatus(CoreConstant.STATUS_DELETE);
                    chequesOfDeedToDeleteVoucherId.add(shrCheque);
                    shrVcrItem.setChequeNo(null);
                    shrVcrItem.setShrCheque(null);
                } else {
                    chequesOfNoDeedToDeleteVoucherId.add(shrCheque);
                }

                if (item.getAccount() != null) {
                    CaseAccountInfo caseAccountInfo
                        = DataUtils.copyProperties(item.getAccount(), CaseAccountInfo.class);
                    shrVcrItem.setCaseAccount(caseAccountInfo);
                }
                if (shrVcrItem.getVoucherItemId() != null){
                    ShrVcrItem one = shrVcrItemRepository.findOne(shrVcrItem.getVoucherItemId());
                    if (one != null){
                        shrVcrItem.setVersionNo(one.getVersionNo());
                    }
                }

                shrVcrItem.setReceiver(item.getPayerName());
                shrVcrItem.setVoucherId(voucherId);
                if (CoreConstant.STATUS_ACTIVE.equals(item.getStatus())) {
                    if (item.getVoucherAmount() != null) {
                        shrVcrItem.setVoucherAmount(item.getVoucherAmount());
                        shrVcrItem.setDebitCredit(ShroffConstant.CR);
                    }
                }
                shrVcrItemRepository.save(shrVcrItem);
            });

        }
        if (CommonUtils.isNotEmpty(chequesOfDeedToDeleteVoucherId)
            && CommonUtils.isNotEmpty(chequesOfNoDeedToDeleteVoucherId)) {
            chequesOfNoDeedToDeleteVoucherId.retainAll(chequesOfDeedToDeleteVoucherId);
            chequesOfDeedToDeleteVoucherId.removeAll(chequesOfNoDeedToDeleteVoucherId);
            if (CommonUtils.isNotEmpty(chequesOfDeedToDeleteVoucherId)) {
                for (ShrCheque cheque : chequesOfDeedToDeleteVoucherId) {
                    cheque.setShrVcrInfo(null);
                    chequeRepository.save(cheque);
                }
            }

        }

        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer savePaymentVoucher(PaymentVoucherDetailDTO voucherDetail) {
        log.info("savePaymentVoucher() start - Payment Voucher Detail = " + voucherDetail);
        if (!WorkFlowAction.Delete.getCode().equals(voucherDetail.getPaymentVoucherBasicInformation().getStatus())) {
            preparePaymentVoucherWorkFlow(voucherDetail.getPaymentVoucherBasicInformation(),
                WorkFlowAction.Save.getCode());
        }
        PaymentVoucherBasicInformationDTO basicInformationDTO = voucherDetail.getPaymentVoucherBasicInformation();

        Long workflowId = null;
        if (basicInformationDTO.getIsAutoPaymentVoucher() != null
                && basicInformationDTO.getIsAutoPaymentVoucher()) {
            workflowId = voucherService.saveAutoWorkFlowHistory(basicInformationDTO.getWorkflowId(),
                    basicInformationDTO.getSysWorkFlowRule(), null);
        } else {
            workflowId = saveWorkFlowHistory(basicInformationDTO.getWorkflowId(),
                    basicInformationDTO.getSysWorkFlowRule(), null);
        }
        basicInformationDTO.setWorkflowId(workflowId);
        Integer voucherId = voucherService.savePaymentVoucherInside(voucherDetail);
        log.info("savePaymentVoucher() end - voucher Id = " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Integer savePaymentVoucherInside(PaymentVoucherDetailDTO voucherDetail) {
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = voucherDetail.getPaymentVoucherBasicInformation();
        Integer voucherItemNo = null;
        if(voucherDetail.getPaymentVoucherItems() != null){
            voucherItemNo = voucherDetail.getPaymentVoucherItems().size();
        }
        
        Voucher voucher = DataUtils.copyProperties(paymentVoucherBasicInformation, Voucher.class);
        VoucherType voucherType = voucherTypeRepository.findByVoucherTypeCode(ShroffConstant.VT_PAY);
        voucher.setVoucherType(voucherType);
        if (paymentVoucherBasicInformation.getControlAccount() != null
            && paymentVoucherBasicInformation.getControlAccount().getCtlAcId() != null
            && paymentVoucherBasicInformation.getControlAccount().getCtlAcId() != 0) {
            voucher.setControlAccount(
                controlAccountRepository.findOne(paymentVoucherBasicInformation.getControlAccount().getCtlAcId()));
        } else {
            voucher.setControlAccount(null);
        }
        if (paymentVoucherBasicInformation.getComPayee() != null
            && paymentVoucherBasicInformation.getComPayee().getPayeeId() != null) {
            voucher.setComPayee(comPayeeRepository.findOne(paymentVoucherBasicInformation.getComPayee().getPayeeId()));
        } else {
            voucher.setComPayee(null);
        }
        voucherRepository.save(voucher);
        Integer voucherId = voucher.getVoucherId();
        List<PaymentVoucherAccountItemDTO> paymentVoucherItems = voucherDetail.getPaymentVoucherItems();
        if (CommonUtils.isNotEmpty(paymentVoucherItems)) {
            for (PaymentVoucherAccountItemDTO item : paymentVoucherItems) {
                ShrVcrItem shrVcrItem = DataUtils.copyProperties(item, ShrVcrItem.class);
                shrVcrItem.setVoucherId(voucherId);
                if (item.getAccount() != null) {
                    CaseAccountInfo caseAccountInfo
                        = DataUtils.copyProperties(item.getAccount(), CaseAccountInfo.class);
                    shrVcrItem.setCaseAccount(caseAccountInfo);
                }
                if (CoreConstant.STATUS_ACTIVE.equals(item.getStatus())) {
                    if (item.getAmount() != null) {
                        shrVcrItem.setVoucherAmount(item.getAmount());
                        shrVcrItem.setDebitCredit(ShroffConstant.DR);
                    }
                }
                if (shrVcrItem.getVoucherItemNo()==null){
                    shrVcrItem.setVoucherItemNo(voucherItemNo);
                }
                shrVcrItemRepository.save(shrVcrItem);
            }
        }

        VoucherDTO voucherDTO = voucherDetail.getVoucherDTO();
        if (voucherDTO != null && voucherDTO.getVoucherId() != null) {
            Map<SysAttachmentDTO, VoucherAttachmentDTO> voucherAttachmentDTOs
                = getVoucherAttachmentDTOs(voucherDTO.getVoucherId());
            if (voucherAttachmentDTOs != null && paymentVoucherBasicInformation.getIsAutoPaymentVoucher()) {
                for (SysAttachmentDTO sysAttachmentDTO : voucherAttachmentDTOs.keySet()) {
                    Integer attachmentId = sysAttachmentService.saveSysAttachment(sysAttachmentDTO);
                    VoucherAttachmentDTO voucherAttachmentDTO = voucherAttachmentDTOs.get(sysAttachmentDTO);
                    voucherAttachmentDTO.setVoucherId(voucherId);
                    voucherAttachmentDTO.setAttachmentId(attachmentId);
                    voucherAttachmentService.saveVoucherAttachment(voucherAttachmentDTO);
                }
            }

            Voucher receiptVoucher = voucherRepository.findVoucherByVoucherId(voucherDTO.getVoucherId());
            receiptVoucher.setIsFinishCrePay(true);
            if (paymentVoucherBasicInformation.getIsAutoPaymentVoucher()) {
                voucherRepository.save(receiptVoucher);
            }
        }

        return voucherId;
    }

    @Transactional(readOnly = true)
    @Override
    public ReceiptVoucherDetailDTO findReceiptVoucher(Integer voucherId) {
        log.info("findReceiptVoucher() start - VoucherId: " + voucherId);
        Voucher voucher = voucherRepository.findOne(voucherId);
        List<ShrVcrItem> shrVcrItems = shrVcrItemRepository.findAvailableVoucherItem(voucherId);
        List<SysApprovalWf> approvalWorkflows = sysApprovalWFRepository
            .findByWorkflowIdAndStatusIgnoreCaseOrderBySeqDesc(voucher.getWorkflowId(), CoreConstant.STATUS_ACTIVE);
        ReceiptVoucherDetailDTO dto = receiptVoucherDetailDTOAssembler.toDTO(voucher, shrVcrItems, approvalWorkflows);
        ActionDTO actionDTO = sysWorkFlowRuleService.findAction(PrivilegeConstant.PRIVILEGE_RVM, voucher.getStatus());
        dto.setAction(actionDTO);
        log.info("findReceiptVoucher() end - " + dto);
        return dto;
    }

    @Transactional(readOnly = true)
    @Override
    public PaymentVoucherDetailDTO findPaymentVoucher(Integer voucherId) {
        log.info("findPaymentVoucher() start - VoucherId: " + voucherId);
        Voucher voucher = voucherRepository.findOne(voucherId);
        List<ShrVcrItem> shrVcrItems = shrVcrItemRepository.findAvailableVoucherItem(voucherId);
        List<SysApprovalWf> approvalWorkflows = sysApprovalWFRepository
            .findByWorkflowIdAndStatusIgnoreCaseOrderBySeqDesc(voucher.getWorkflowId(), CoreConstant.STATUS_ACTIVE);
        ShrPendingMoney pendingMoney
            = pendingMoneyRepository.findByPaymentVoucherIdAndStatus(voucherId, CoreConstant.STATUS_ACTIVE);
        PaymentVoucherDetailDTO dto
            = paymentVoucherDetailDTOAssembler.toDTO(voucher, shrVcrItems, approvalWorkflows, pendingMoney);
        dto.getPaymentVoucherBasicInformation().setComPayee(commonPayeeDTOAssembler.toDTO(voucher.getComPayee()));
        ActionDTO actionDTO = sysWorkFlowRuleService.findAction(PrivilegeConstant.PRIVILEGE_PVM, voucher.getStatus());
        dto.setAction(actionDTO);
        log.info("findPaymentVoucher() end - " + dto);
        return dto;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void deleteVoucher(Integer voucherId) {
        log.info("deleteVoucher() start - VoucherId: " + voucherId);
        List<ShrVcrItem> shrVcrItems = shrVcrItemRepository.findByVoucherId(voucherId);
        shrVcrItems.stream().forEach(item -> {
            item.setStatus(CoreConstant.VOUCHER_STATUS_DELETED);
            shrVcrItemRepository.save(item);
        });

        Voucher voucher = voucherRepository.findOne(voucherId);
        voucher.setStatus(CoreConstant.VOUCHER_STATUS_DELETED);
        voucherRepository.save(voucher);
        log.info("deleteVoucher() end - ");
    }

    @Transactional(readOnly = true)
    @Override
    public DownloadFileDTO downloadImportTemplate(List<String> header) {
        log.info("downloadImportTemplate() start - header: " + header);
        StringBuilder sb = new StringBuilder();
        CSVPrinter csvFilePrinter = null;
        CSVFormat csvFileFormat = CSVFormat.DEFAULT.withRecordSeparator(CoreConstant.CSV_LINE_END);

        try {
            csvFilePrinter = new CSVPrinter(sb, csvFileFormat);
            csvFilePrinter.printRecord(header.subList(0, header.size() / 2));
            csvFilePrinter.printRecord(header.subList(header.size() / 2, header.size()));
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        } finally {
            try {
                csvFilePrinter.flush();
            } catch (Exception oe) {
                log.error(oe.getMessage(), oe);
            }
        }
        DownloadFileDTO downloadFileDTO = new DownloadFileDTO();
        downloadFileDTO.setFileFormat(MimeTypeUtils.FileType.CSV.name());
        downloadFileDTO.setFileName(TEMPLATE_FILE_NAME);
        downloadFileDTO.setFileResult(sb.toString().getBytes());
        log.info("downloadImportTemplate() end - result = " + downloadFileDTO);
        return downloadFileDTO;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer submitJournalVoucher(JournalVoucherDetailDTO journalVoucherDetail) {
        log.info("submitJournalVoucher() start - " + journalVoucherDetail);
        prepareJournalVoucherWorkFlow(journalVoucherDetail.getJournalVoucherBasicInformation(),
            WorkFlowAction.Submit.getCode());
        JournalVoucherBasicInformationDTO journalVoucherBasicInformation
            = journalVoucherDetail.getJournalVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(journalVoucherBasicInformation.getWorkflowId(),
            journalVoucherBasicInformation.getSysWorkFlowRule(), null);

        if (CommonUtils.isBlank(journalVoucherBasicInformation.getVoucherNo())) {
            String voucherNumber = genVoucherNumber(journalVoucherBasicInformation.getGroupCode(),
                ShroffConstant.VT_JOU, SysSequenceEnum.JOU_VOUCHER_SEQ_NO.name());
            journalVoucherBasicInformation.setVoucherNo(voucherNumber);
        }

        journalVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = saveJournalVoucherInside(journalVoucherDetail);

        Map<String, Map> resultMap = calculateJournalVoucherItemOnHoldAmount(
            journalVoucherDetail.getJournalVoucherAccountItems(),
            journalVoucherDetail.getJournalVoucherBasicInformation().getSysWorkFlowRule().getAction().getCodeValue());
        Map<Integer, CaseAccountInfoDTO> accountMap = resultMap.get("accountMap");
        Map<Integer, ControlAccountDTO> ctlAcMap = resultMap.get("ctlAcMap");
        saveCaseAccountInfos(accountMap.values());
        saveControlAccountInfos(ctlAcMap.values());

        log.info("submitJournalVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    private Map<String, Map> calculateJournalVoucherItemOnHoldAmount(
        List<JournalVoucherAccountItemDTO> journalVoucherAccountItemDTOS, String action) {
        Map<String, Map> resultMap = new HashMap<>();
        Map<Integer,
            CaseAccountInfoDTO> accountMap = journalVoucherAccountItemDTOS.stream()
                .filter(
                    item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus()) && Objects.nonNull(item.getAccount()))
                .collect(Collectors.toMap(e -> e.getAccount().getCaseAcId(), JournalVoucherAccountItemDTO::getAccount,
                    (v1, v2) -> v1));
        Map<Integer, ControlAccountDTO> ctlAcMap = journalVoucherAccountItemDTOS.stream().filter(
            item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus()) && Objects.nonNull(item.getControlAccount()))
            .collect(Collectors.toMap(e -> e.getControlAccount().getCtlAcId(),
                JournalVoucherAccountItemDTO::getControlAccount, (v1, v2) -> v1));

        Map<Integer, BigDecimal> accountAmountCrMap = journalVoucherAccountItemDTOS.stream()
            .filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus()) && Objects.nonNull(item.getAccount()))
            .collect(Collectors.groupingBy(e -> e.getAccount().getCaseAcId())).entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey,
                e -> e.getValue().stream().filter(item -> Objects.nonNull(item.getAmountCr()))
                    .map(JournalVoucherAccountItemDTO::getAmountCr).reduce(BigDecimal.ZERO, BigDecimal::add)));
        Map<Integer, BigDecimal> accountAmountDrMap = journalVoucherAccountItemDTOS.stream()
            .filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus()) && Objects.nonNull(item.getAccount()))
            .collect(Collectors.groupingBy(e -> e.getAccount().getCaseAcId())).entrySet().stream()
            .collect(Collectors.toMap(Map.Entry::getKey,
                e -> e.getValue().stream().filter(item -> Objects.nonNull(item.getAmountDr()))
                    .map(JournalVoucherAccountItemDTO::getAmountDr).reduce(BigDecimal.ZERO, BigDecimal::add)));

        Map<Integer,
            BigDecimal> ctlAcAmountCRMap = journalVoucherAccountItemDTOS.stream()
                .filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus())
                    && Objects.nonNull(item.getControlAccount()))
                .collect(Collectors.groupingBy(e -> e.getControlAccount().getCtlAcId())).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey,
                    e -> e.getValue().stream().filter(item -> Objects.nonNull(item.getAmountCr()))
                        .map(JournalVoucherAccountItemDTO::getAmountCr).reduce(BigDecimal.ZERO, BigDecimal::add)));
        Map<Integer,
            BigDecimal> ctlAcAmountDRMap = journalVoucherAccountItemDTOS.stream()
                .filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus())
                    && Objects.nonNull(item.getControlAccount()))
                .collect(Collectors.groupingBy(e -> e.getControlAccount().getCtlAcId())).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey,
                    e -> e.getValue().stream().filter(item -> Objects.nonNull(item.getAmountDr()))
                        .map(JournalVoucherAccountItemDTO::getAmountDr).reduce(BigDecimal.ZERO, BigDecimal::add)));
        switch (action) {
            case CoreConstant.VOUCHER_STATUS_SUBMITTED:
                accountMap.forEach(
                    (k, v) -> v.setOnHoldAmountCr(CommonUtils.add(v.getOnHoldAmountCr(), accountAmountCrMap.get(k))));
                accountMap.forEach(
                    (k, v) -> v.setOnHoldAmountDr(CommonUtils.add(v.getOnHoldAmountDr(), accountAmountDrMap.get(k))));
                ctlAcMap.forEach(
                    (k, v) -> v.setOnHoldAmountDr(CommonUtils.add(v.getOnHoldAmountDr(), ctlAcAmountDRMap.get(k))));
                ctlAcMap.forEach(
                    (k, v) -> v.setOnHoldAmountCr(CommonUtils.add(v.getOnHoldAmountCr(), ctlAcAmountCRMap.get(k))));
                break;
            case CoreConstant.VOUCHER_STATUS_REJECTED:
            case CoreConstant.VOUCHER_STATUS_REVERSED:
                accountMap.forEach((k, v) -> v
                    .setOnHoldAmountCr(CommonUtils.subtract(v.getOnHoldAmountCr(), accountAmountCrMap.get(k))));
                accountMap.forEach((k, v) -> v
                    .setOnHoldAmountDr(CommonUtils.subtract(v.getOnHoldAmountDr(), accountAmountDrMap.get(k))));
                ctlAcMap.forEach((k, v) -> v
                    .setOnHoldAmountCr(CommonUtils.subtract(v.getOnHoldAmountCr(), ctlAcAmountCRMap.get(k))));
                ctlAcMap.forEach((k, v) -> v
                    .setOnHoldAmountDr(CommonUtils.subtract(v.getOnHoldAmountDr(), ctlAcAmountDRMap.get(k))));
                break;
            default:
                break;
        }
        resultMap.put("accountMap", accountMap);
        resultMap.put("ctlAcMap", ctlAcMap);
        return resultMap;
    }

    private String genVoucherNumber(String groupCode, String voucherTypeCode, String seqCode) {
        log.info("genVoucherNumber() start - GroupCode: " + groupCode + ", VoucherTypeCode: " + voucherTypeCode);
        String generateVoucherSeqNo = sysSequenceService.generateVoucherSeqNo(seqCode);
        String voucherNumber = groupCode + voucherTypeCode + generateVoucherSeqNo;
        log.info("genVoucherNumber() end - VoucherNumber: " + voucherNumber);
        return voucherNumber;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public Long saveWorkFlowHistory(Long workflowId, SysWorkFlowRuleDTO sysWorkFlowRuleDTO, String remark) {
        log.info("saveWorkFlowHistory() start - WorkFlowId: " + workflowId + ", SysWorkFlowRuleDTO: "
            + sysWorkFlowRuleDTO + ", Remark: " + remark);
        UserInfo currentUserInfo = RequestContextUtils.getCurrentUserInfo();
        if (sysWorkFlowRuleDTO != null) {
            ApplicationCodeTableDTO actionDone = sysWorkFlowRuleDTO.getActionDone();
            if (actionDone != null) {
                SysApprovalWfDTO sysApprovalWf = new SysApprovalWfDTO();
                sysApprovalWf.setAction(actionDone.getCodeDesc());
                sysApprovalWf.setActionByPerson(currentUserInfo.getUserName());
                sysApprovalWf.setActionByPostId(currentUserInfo.getPostId());
                sysApprovalWf.setDivisionId(currentUserInfo.getDivisionId());
                sysApprovalWf.setPrivilegeCode(sysWorkFlowRuleDTO.getPrivilege().getPrivilegeCode());
                sysApprovalWf.setRemark(remark);
                workflowId = commonService.saveSysApprovalWf(workflowId, sysApprovalWf);
            }
        }

        if (sysWorkFlowRuleDTO != null) {
            ApplicationCodeTableDTO actionToBeTaken = sysWorkFlowRuleDTO.getActionToBeTaken();
            if (actionToBeTaken != null) {
                SysApprovalWfDTO sysApprovalWf = new SysApprovalWfDTO();
                sysApprovalWf.setAction(actionToBeTaken.getCodeDesc());
                sysApprovalWf.setActionByPerson(currentUserInfo.getUserName());
                sysApprovalWf.setActionByPostId(currentUserInfo.getPostId());
                sysApprovalWf.setDivisionId(currentUserInfo.getDivisionId());
                sysApprovalWf.setPrivilegeCode(sysWorkFlowRuleDTO.getPrivilege().getPrivilegeCode());
                sysApprovalWf.setRemark(remark);
                workflowId = commonService.saveSysApprovalWf(workflowId, sysApprovalWf);
            }
        }

        log.info("saveWorkFlowHistory() end - WorkFlowId: " + workflowId);
        return workflowId;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public Long saveAutoWorkFlowHistory(Long workflowId, SysWorkFlowRuleDTO sysWorkFlowRuleDTO, String remark) {
        log.info("saveWorkFlowHistory() start - WorkFlowId: " + workflowId + ", SysWorkFlowRuleDTO: "
            + sysWorkFlowRuleDTO + ", Remark: " + remark);
        UserInfo currentUserInfo = new UserInfo();
        Post userInfo = getUserInfo();
        currentUserInfo.setPostId(userInfo.getPostId());
        currentUserInfo.setDivisionId(userInfo.getDivision().getDivisionId());
        currentUserInfo.setUserName(userInfo.getPostTitle());
        if (sysWorkFlowRuleDTO != null) {
            ApplicationCodeTableDTO actionDone = sysWorkFlowRuleDTO.getActionDone();
            if (actionDone != null) {
                SysApprovalWfDTO sysApprovalWf = new SysApprovalWfDTO();
                sysApprovalWf.setAction(actionDone.getCodeDesc());
                sysApprovalWf.setActionByPerson(currentUserInfo.getUserName());
                sysApprovalWf.setActionByPostId(currentUserInfo.getPostId());
                sysApprovalWf.setDivisionId(currentUserInfo.getDivisionId());
                sysApprovalWf.setPrivilegeCode(sysWorkFlowRuleDTO.getPrivilege().getPrivilegeCode());
                sysApprovalWf.setRemark(remark);
                sysApprovalWf.setCreatedByPost("SYSTEM");
                sysApprovalWf.setCreatedByUser("system");
                sysApprovalWf.setLastUpdatedByPost("SYSTEM");
                sysApprovalWf.setLastUpdatedByUser("SYSTEM");
                sysApprovalWf.setTxnKeyRef(sysSequenceService.generateTxnKeyRefNo());
                workflowId = commonService.saveSysApprovalWf(workflowId, sysApprovalWf);
            }
        }

        if (sysWorkFlowRuleDTO != null) {
            ApplicationCodeTableDTO actionToBeTaken = sysWorkFlowRuleDTO.getActionToBeTaken();
            if (actionToBeTaken != null) {
                SysApprovalWfDTO sysApprovalWf = new SysApprovalWfDTO();
                sysApprovalWf.setAction(actionToBeTaken.getCodeDesc());
                sysApprovalWf.setActionByPerson(currentUserInfo.getUserName());
                sysApprovalWf.setActionByPostId(currentUserInfo.getPostId());
                sysApprovalWf.setDivisionId(currentUserInfo.getDivisionId());
                sysApprovalWf.setPrivilegeCode(sysWorkFlowRuleDTO.getPrivilege().getPrivilegeCode());
                sysApprovalWf.setRemark(remark);
                sysApprovalWf.setCreatedByPost("SYSTEM");
                sysApprovalWf.setCreatedByUser("system");
                sysApprovalWf.setLastUpdatedByPost("SYSTEM");
                sysApprovalWf.setLastUpdatedByUser("SYSTEM");
                sysApprovalWf.setTxnKeyRef(sysSequenceService.generateTxnKeyRefNo());
                workflowId = commonService.saveSysApprovalWf(workflowId, sysApprovalWf);
            }
        }

        log.info("saveWorkFlowHistory() end - WorkFlowId: " + workflowId);
        return workflowId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer approveJournalVoucher(JournalVoucherDetailDTO journalVoucherDetail) {
        log.info("approveJournalVoucher() start - " + journalVoucherDetail);
        prepareJournalVoucherWorkFlow(journalVoucherDetail.getJournalVoucherBasicInformation(),
            WorkFlowAction.Approve.getCode());
        JournalVoucherBasicInformationDTO journalVoucherBasicInformation
            = journalVoucherDetail.getJournalVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(journalVoucherBasicInformation.getWorkflowId(),
            journalVoucherBasicInformation.getSysWorkFlowRule(), null);

        journalVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = saveJournalVoucherInside(journalVoucherDetail);

        log.info("approveJournalVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer rejectJournalVoucher(JournalVoucherDetailDTO journalVoucherDetail) {
        log.info("rejectJournalVoucher() start - " + journalVoucherDetail);
        prepareJournalVoucherWorkFlow(journalVoucherDetail.getJournalVoucherBasicInformation(),
            WorkFlowAction.Reject.getCode());
        List<ErrorMsg> errorMsgs = new ArrayList<>();
        Set<String> notEnoughOnholdCredit = new HashSet<>();
        Set<String> notEnoughOnholdDebit = new HashSet<>();
        Set<String> ctlNotEnoughOnholdCredit = new HashSet<>();
        Set<String> ctlNotEnoughOnholdDebit = new HashSet<>();
        JournalVoucherBasicInformationDTO journalVoucherBasicInformation
            = journalVoucherDetail.getJournalVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(journalVoucherBasicInformation.getWorkflowId(),
            journalVoucherBasicInformation.getSysWorkFlowRule(), journalVoucherDetail.getRejectRemark());
        journalVoucherBasicInformation.setWorkflowId(workflowId);
        // journalVoucherBasicInformation.setFirstApproverId(null);
        journalVoucherBasicInformation.setPaymentVerifierId(null);
        Integer voucherId = saveJournalVoucherInside(journalVoucherDetail);

        Map<String, Map> resultMap = calculateJournalVoucherItemOnHoldAmount(
            journalVoucherDetail.getJournalVoucherAccountItems(),
            journalVoucherDetail.getJournalVoucherBasicInformation().getSysWorkFlowRule().getAction().getCodeValue());
        Map<Integer, CaseAccountInfoDTO> accountMap = resultMap.get("accountMap");
        Map<Integer, ControlAccountDTO> ctlAcMap = resultMap.get("ctlAcMap");
        // remove follow code because BC1 and BC11 can not reject in journal voucher on prod
        // accountMap.entrySet().stream().filter(e -> BigDecimal.ZERO.compareTo(e.getValue().getOnHoldAmountCr()) > 0)
        // .forEach(v -> notEnoughOnholdCredit.add(v.getValue().getCaseAccountName()));
        // accountMap.entrySet().stream().filter(e -> BigDecimal.ZERO.compareTo(e.getValue().getOnHoldAmountDr()) > 0)
        // .forEach(v -> notEnoughOnholdDebit.add(v.getValue().getCaseAccountName()));
        // ctlAcMap.entrySet().stream().filter(e -> BigDecimal.ZERO.compareTo(e.getValue().getOnHoldAmountCr()) > 0)
        // .forEach(v -> ctlNotEnoughOnholdCredit.add(v.getValue().getCtlAcName()));
        // ctlAcMap.entrySet().stream().filter(e -> BigDecimal.ZERO.compareTo(e.getValue().getOnHoldAmountDr()) > 0)
        // .forEach(v -> ctlNotEnoughOnholdDebit.add(v.getValue().getCtlAcName()));

        if (CommonUtils.isNotEmpty(notEnoughOnholdCredit)) {
            errorMsgs.add(new ErrorMsg(MsgCodeConstant.MSG_ONHOLD_CREDIT_NOT_ENOUGH_FOR_CREDIT,
                MsgParamCodeConstant.CASE_ACCOUNT, genCollectionStr(notEnoughOnholdCredit)));
        }
        if (CommonUtils.isNotEmpty(notEnoughOnholdDebit)) {
            errorMsgs.add(new ErrorMsg(MsgCodeConstant.MSG_ONHOLD_DEBIT_NOT_ENOUGH_FOR_DEBIT,
                MsgParamCodeConstant.CASE_ACCOUNT, genCollectionStr(notEnoughOnholdDebit)));
        }
        if (CommonUtils.isNotEmpty(ctlNotEnoughOnholdCredit)) {
            errorMsgs.add(new ErrorMsg(MsgCodeConstant.MSG_ONHOLD_CREDIT_NOT_ENOUGH_FOR_CREDIT,
                MsgParamCodeConstant.CONTROL_ACCOUNT, genCollectionStr(notEnoughOnholdCredit)));
        }
        if (CommonUtils.isNotEmpty(ctlNotEnoughOnholdDebit)) {
            errorMsgs.add(new ErrorMsg(MsgCodeConstant.MSG_ONHOLD_DEBIT_NOT_ENOUGH_FOR_DEBIT,
                MsgParamCodeConstant.CONTROL_ACCOUNT, genCollectionStr(notEnoughOnholdDebit)));
        }
        if (CommonUtils.isNotEmpty(errorMsgs)) {
            throw new BusinessException("Reject Journal Voucher Failed", errorMsgs);
        }
        saveCaseAccountInfos(accountMap.values());
        saveControlAccountInfos(ctlAcMap.values());

        log.info("rejectJournalVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveCaseAccountInfos(Collection<CaseAccountInfoDTO> collection) {
        if (CommonUtils.isNotEmpty(collection)) {
            collection.stream().forEach(account -> {
                CaseAccountInfo caseAccountInfo = DataUtils.copyProperties(account, CaseAccountInfo.class);
                caseAccountInfoRepository.save(caseAccountInfo);
            });
        }
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer verifyJournalVoucher(JournalVoucherDetailDTO journalVoucherDetail) {
        log.info("verifyJournalVoucher() start - " + journalVoucherDetail);
        prepareJournalVoucherWorkFlow(journalVoucherDetail.getJournalVoucherBasicInformation(),
            WorkFlowAction.Verify.getCode());
        JournalVoucherBasicInformationDTO journalVoucherBasicInformation
            = journalVoucherDetail.getJournalVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(journalVoucherBasicInformation.getWorkflowId(),
            journalVoucherBasicInformation.getSysWorkFlowRule(), null);

        journalVoucherBasicInformation.setWorkflowId(workflowId);
        journalVoucherBasicInformation.setPaymentVerifierId(RequestContextUtils.getCurrentUserInfo().getPostId());
        journalVoucherBasicInformation.setPaymentApproverName(RequestContextUtils.getCurrentUserInfo().getUserName());
        Integer voucherId = saveJournalVoucherInside(journalVoucherDetail);

        log.info("verifyJournalVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer reverseJournalVoucher(JournalVoucherDetailDTO journalVoucherDetail) {
        log.info("reverseJournalVoucher() start - " + journalVoucherDetail);
        prepareJournalVoucherWorkFlow(journalVoucherDetail.getJournalVoucherBasicInformation(),
            WorkFlowAction.Reverse.getCode());
        JournalVoucherBasicInformationDTO journalVoucherBasicInformation
            = journalVoucherDetail.getJournalVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(journalVoucherBasicInformation.getWorkflowId(),
            journalVoucherBasicInformation.getSysWorkFlowRule(), journalVoucherDetail.getRejectRemark());

        journalVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = saveJournalVoucherInside(journalVoucherDetail);
        Map<String, Map> resultMap = calculateJournalVoucherItemOnHoldAmount(
            journalVoucherDetail.getJournalVoucherAccountItems(),
            journalVoucherDetail.getJournalVoucherBasicInformation().getSysWorkFlowRule().getAction().getCodeValue());
        Map<Integer, CaseAccountInfoDTO> accountMap = resultMap.get("accountMap");
        Map<Integer, ControlAccountDTO> ctlAcMap = resultMap.get("ctlAcMap");

        saveCaseAccountInfos(accountMap.values());
        saveControlAccountInfos(ctlAcMap.values());
        log.info("reverseJournalVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer deleteJournalVoucher(JournalVoucherDetailDTO journalVoucherDetail) {
        log.info("deleteJournalVoucher() start - " + journalVoucherDetail);
        prepareJournalVoucherWorkFlow(journalVoucherDetail.getJournalVoucherBasicInformation(),
            WorkFlowAction.Delete.getCode());
        JournalVoucherBasicInformationDTO journalVoucherBasicInformation
            = journalVoucherDetail.getJournalVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(journalVoucherBasicInformation.getWorkflowId(),
            journalVoucherBasicInformation.getSysWorkFlowRule(), null);

        journalVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = saveJournalVoucherInside(journalVoucherDetail);

        log.info("deleteJournalVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer submitReceiptVoucher(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        log.info("submitReceiptVoucher() start - receiptVoucherDetail: " + receiptVoucherDetail);
        prepareReceiptVoucherWorkFlow(receiptVoucherDetail.getReceiptVoucherBasicInformation(),
            WorkFlowAction.Submit.getCode());
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo = receiptVoucherDetail.getReceiptVoucherBasicInformation();
        Long workflowId = saveWorkFlowHistory(receiptVoucherBasicInfo.getWorkflowId(),
            receiptVoucherBasicInfo.getSysWorkFlowRule(), null);

        if (CommonUtils.isBlank(receiptVoucherBasicInfo.getVoucherNo())) {
            String voucherNumber = genVoucherNumber(receiptVoucherBasicInfo.getGroupCode(), ShroffConstant.VT_REC,
                SysSequenceEnum.REC_VOUCHER_SEQ_NO.name());
            receiptVoucherBasicInfo.setVoucherNo(voucherNumber);
        }

        receiptVoucherBasicInfo.setWorkflowId(workflowId);

        Integer voucherId = saveReceiptVoucherInside(receiptVoucherDetail);

        List<ReceiptVoucherAccountItemDTO> receiptVoucherAccountItems
            = receiptVoucherDetail.getReceiptVoucherAccountItems().stream()
                .filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus())).collect(Collectors.toList());
        Map<Integer, CaseAccountInfoDTO> accountMap = new HashMap<>();
        Map<Integer, ControlAccountDTO> ctlAccountMap = new HashMap<>();
        Map<Integer, ChequeDTO> chequeDTOMap = new HashMap<>();
        for (ReceiptVoucherAccountItemDTO item : receiptVoucherAccountItems) {
            if (item.getAccount() != null && item.getAccount().getCaseAcId() != null
                && item.getAccount().getCaseAcId() != 0) {
                Integer caseAcId = item.getAccount().getCaseAcId();
                CaseAccountInfoDTO account = null;
                if (accountMap.containsKey(caseAcId)) {
                    account = accountMap.get(caseAcId);
                } else {
                    account = item.getAccount();
                    accountMap.put(caseAcId, account);
                }
                account.setOnHoldAmountCr(account.getOnHoldAmountCr().add(item.getVoucherAmount()));
            } else if (item.getControlAccount() != null && item.getControlAccount().getCtlAcId() != null
                && item.getControlAccount().getCtlAcId() != 0) {
                Integer ctlAcId = item.getControlAccount().getCtlAcId();
                ControlAccountDTO account = null;
                if (ctlAccountMap.containsKey(ctlAcId)) {
                    account = ctlAccountMap.get(ctlAcId);
                } else {
                    account = item.getControlAccount();
                    ctlAccountMap.put(ctlAcId, account);
                }
                account.setOnHoldAmountCr(account.getOnHoldAmountCr().add(item.getVoucherAmount()));
            }

            if (item.getAccount() != null
                && receiptVoucherDetail.getReceiptVoucherBasicInformation().getRealizationFee() != null
                && receiptVoucherDetail.getReceiptVoucherBasicInformation().getRealizationFee()) {
                Map<String, Object> map = handleRealizationFee(DateUtils.getCurrentDate(), ctlAccountMap,
                    item.getAccount(), item.getAnalysisCode());
                Boolean chargeRealizationFee = (Boolean)map.get("chargeRealizationFee");
                if (chargeRealizationFee) {
                    ControlAccountDTO controlAccountDTO = (ControlAccountDTO)map.get("controlAccountDTO");
                    if (ctlAccountMap.containsKey(controlAccountDTO.getCtlAcId())) {
                        controlAccountDTO = ctlAccountMap.get(controlAccountDTO.getCtlAcId());
                    }
                    CaseAccountInfoDTO account = item.getAccount();
                    if (accountMap.containsKey(account.getCaseAcId())) {
                        account = accountMap.get(account.getCaseAcId());
                    }
                    BigDecimal realizationFee = (BigDecimal)map.get("realizationFee");
                    controlAccountDTO
                        .setOnHoldAmountCr(CommonUtils.add(controlAccountDTO.getOnHoldAmountCr(), realizationFee));
                    account.setOnHoldAmountDr(CommonUtils.add(account.getOnHoldAmountDr(), realizationFee));
                }
            }

            ChequeDTO chequeDTO = item.getCheque();
            if (chequeDTO != null && chequeDTO.getChequeId() != null) {
                chequeDTOMap.put(chequeDTO.getChequeId(), chequeDTO);
            }
        }
        this.saveCheques(chequeDTOMap.values(), voucherId);
        this.saveCaseAccountInfos(accountMap.values());
        saveControlAccountInfos(ctlAccountMap.values());

        log.info("submitReceiptVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    private void saveCheques(Collection<ChequeDTO> chequeDTOS, Integer voucherId) {
        chequeDTOS.forEach((chequeDTO -> {
            chequeDTO.setStatus(CoreConstant.CHEQUE_STATUS_EXECUTED);
            VoucherDTO tmp = new VoucherDTO();
            tmp.setVoucherId(voucherId);
            chequeDTO.setShrVcrInfo(tmp);
            chequeService.saveIncomingCheque(chequeDTO);
            chequeService.syncReceiveDeposit(chequeDTO.getChequeId());
        }));
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer submitPaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("submitPaymentVoucher() start - paymentVoucherDetail : " + paymentVoucherDetail);
        Integer voucherId = submitPaymentVoucher(paymentVoucherDetail, false);
        

        log.info("submitPaymentVoucher end - " + voucherId);
        return voucherId;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer submitPaymentVoucherForSecondApprove(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("submitPaymentVoucherForSecondApprove() start - paymentVoucherDetail : " + paymentVoucherDetail);
        // TODO
        preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
            WorkFlowAction.SubmitFor2ndApprove.getCode());
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = paymentVoucherDetail.getPaymentVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
            paymentVoucherBasicInformation.getSysWorkFlowRule(), null);

        paymentVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = savePaymentVoucherInside(paymentVoucherDetail);
        log.info("submitPaymentVoucherForSecondApprove end - " + voucherId);
        return voucherId;
    }

    private Integer submitPaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail, boolean withPending) {
        if (!withPending) {
            preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
                WorkFlowAction.Submit.getCode());
        } else {
            preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
                WorkFlowAction.SubmitWithPending.getCode());
        }
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = paymentVoucherDetail.getPaymentVoucherBasicInformation();

        Long workflowId = null;
        if (paymentVoucherBasicInformation.getIsAutoPaymentVoucher() != null
            && paymentVoucherBasicInformation.getIsAutoPaymentVoucher()) {
            workflowId = voucherService.saveAutoWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
                paymentVoucherBasicInformation.getSysWorkFlowRule(), null);
        } else {
            workflowId = saveWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
                paymentVoucherBasicInformation.getSysWorkFlowRule(), null);
        }

        if (CommonUtils.isBlank(paymentVoucherBasicInformation.getVoucherNo())) {
            String voucherNumber = genVoucherNumber(paymentVoucherBasicInformation.getGroupCode(),
                ShroffConstant.VT_PAY, SysSequenceEnum.PAY_VOUCHER_SEQ_NO.name());
            paymentVoucherBasicInformation.setVoucherNo(voucherNumber);
        }

        paymentVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = voucherService.savePaymentVoucherInside(paymentVoucherDetail);

        List<PaymentVoucherAccountItemDTO> paymentVoucherAccountItems = paymentVoucherDetail.getPaymentVoucherItems()
            .stream().filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus())).collect(Collectors.toList());
        Map<Integer, CaseAccountInfoDTO> accountMap = new HashMap<>();
        Map<Integer, ControlAccountDTO> ctlAccountMap = new HashMap<>();
        Map<Integer, ControlAccountDTO> lastCtlAcMap = new HashMap<>();
        for (PaymentVoucherAccountItemDTO item : paymentVoucherAccountItems) {
            ControlAccountDTO ctlAccount = null;
            if (item.getAccount() != null && item.getAccount().getCaseAcId() != null
                && item.getAccount().getCaseAcId() != 0) {
                Integer caseAcId = item.getAccount().getCaseAcId();
                CaseAccountInfoDTO account = null;
                if (accountMap.containsKey(caseAcId)) {
                    account = accountMap.get(caseAcId);
                } else {
                    account = item.getAccount();
                    accountMap.put(caseAcId, account);
                }
                account.setOnHoldAmountDr(account.getOnHoldAmountDr().add(item.getAmount()));
            } else if (item.getControlAccount() != null && item.getControlAccount().getCtlAcId() != null
                && item.getControlAccount().getCtlAcId() != 0) {
                Integer ctlAcId = item.getControlAccount().getCtlAcId();
                // ControlAccountDTO account = null;
                if (accountMap.containsKey(ctlAcId)) {
                    ctlAccount = ctlAccountMap.get(ctlAcId);
                } else {
                    if (!lastCtlAcMap.isEmpty() && lastCtlAcMap.containsKey(ctlAcId)) {
                        ctlAccount = lastCtlAcMap.get(ctlAcId);
                    } else {
                        ctlAccount = item.getControlAccount();
                    }
                    ctlAccountMap.put(ctlAcId, ctlAccount);
                }
                ctlAccount.setOnHoldAmountDr(ctlAccount.getOnHoldAmountDr().add(item.getAmount()));
            }
            if (ctlAccount != null) {
                lastCtlAcMap.put(ctlAccount.getCtlAcId(), ctlAccount);
            }
        }
        
        voucherService.saveCaseAccountInfos(accountMap.values());
        voucherService.saveControlAccountInfos(ctlAccountMap.values());
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer approveReceiptVoucher(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        log.info("approveReceiptVoucher() start - receiptVoucherDetailDTO: " + receiptVoucherDetail);
        prepareReceiptVoucherWorkFlow(receiptVoucherDetail.getReceiptVoucherBasicInformation(),
            WorkFlowAction.Approve.getCode());
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo = receiptVoucherDetail.getReceiptVoucherBasicInformation();

        Long workflowId = this.saveWorkFlowHistory(receiptVoucherBasicInfo.getWorkflowId(),
            receiptVoucherBasicInfo.getSysWorkFlowRule(), null);

        receiptVoucherBasicInfo.setWorkflowId(workflowId);
        Integer voucherId = this.saveReceiptVoucherInside(receiptVoucherDetail);

        Boolean isNewOrNot = receiptVoucherBasicInfo.getIsNewOrNot();
        if(isNewOrNot != null && isNewOrNot && voucherId != null){
            Voucher receiptVoucher = voucherRepository.findVoucherByVoucherId(voucherId);
            VoucherDTO voucherDTO = DataUtils.copyProperties(receiptVoucher, VoucherDTO.class);
            JournalVoucherDetailDTO journalVoucherDetailDTO = new JournalVoucherDetailDTO();
            journalVoucherDetailDTO.setJournalVoucherBasicInformation(getJournalVoucherBasicInformation(receiptVoucher));
            journalVoucherDetailDTO.getJournalVoucherBasicInformation().setIsNewOrNot(isNewOrNot);
            journalVoucherDetailDTO.setVoucherDTO(voucherDTO);
            journalVoucherDetailDTO.setJournalVoucherAccountItems(getJournalVoucherAccountItems(receiptVoucher));
            voucherService.saveJournalVoucher(journalVoucherDetailDTO);
        }

        log.info("approveReceiptVoucher() start - receiptVoucherDetailDTO: " + receiptVoucherDetail);
        return voucherId;
    }
    
    private JournalVoucherBasicInformationDTO getJournalVoucherBasicInformation(Voucher voucher){
        log.info("getJournalVoucherBasicInformation() start - voucher: " + voucher);
        JournalVoucherBasicInformationDTO journalVoucherBasicInformationDTO = new JournalVoucherBasicInformationDTO();
        journalVoucherBasicInformationDTO.setVoucherType(null);
        JournalTypeDTO journalTypeDTO = new JournalTypeDTO();
        journalTypeDTO.setJournalTypeId(1);
        journalTypeDTO.setJournalTypeCode("TR");
        journalTypeDTO.setJournalTypeName("TR");
        journalTypeDTO.setJournalTypeDesc("Journal Type TR");
        journalVoucherBasicInformationDTO.setJournalType(journalTypeDTO);
        journalVoucherBasicInformationDTO.setFileRef(voucher.getFileRef());
        journalVoucherBasicInformationDTO.setGroupCode(voucher.getGroupCode());
        journalVoucherBasicInformationDTO.setVoucherNo(null);
        journalVoucherBasicInformationDTO.setVoucherSource(voucher.getVoucherNo());
        Calendar voucherDate = Calendar.getInstance();
        voucherDate.setTime(new Date());
        voucherDate.set(Calendar.SECOND, 0);
        voucherDate.set(Calendar.MINUTE, 0);
        voucherDate.set(Calendar.HOUR_OF_DAY, 0);
        voucherDate.set(Calendar.MILLISECOND, 0);
        journalVoucherBasicInformationDTO.setVoucherDate(voucherDate.getTime());
        journalVoucherBasicInformationDTO.setVoucherTotalAmount(voucher.getVoucherTotalAmount());
        Post post = postService.findByPostTitle("CO(SO)2");
        journalVoucherBasicInformationDTO.setPreparerId(post.getPostId());
        journalVoucherBasicInformationDTO.setRemark(voucher.getRemark());
        journalVoucherBasicInformationDTO.setIsHardCopy(voucher.getIsHardCopy());
        journalVoucherBasicInformationDTO.setCurrencyId(voucher.getCurrencyId());
        journalVoucherBasicInformationDTO.setStatus(CoreConstant.VOUCHER_STATUS_DRAFT);
        journalVoucherBasicInformationDTO.setPoolRelated(voucher.getPoolRelated());
        journalVoucherBasicInformationDTO.setRealizationFee(voucher.getRealizationFee());
        journalVoucherBasicInformationDTO.setSourceCode(voucher.getSourceCode());
        journalVoucherBasicInformationDTO.setIsAutoPaymentVoucher(true);

        log.info("getJournalVoucherBasicInformation() end - journalVoucherBasicInformationDTO: " + journalVoucherBasicInformationDTO);
        return journalVoucherBasicInformationDTO;
    }
    
    private List<JournalVoucherAccountItemDTO> getJournalVoucherAccountItems(Voucher voucher){
        log.info("getJournalVoucherAccountItems() start - voucher: " + voucher);
        List<ShrVcrItem> shrVcrItems = shrVcrItemRepository.findByVoucherId(voucher.getVoucherId());
        List<JournalVoucherAccountItemDTO> journalVoucherAccountItems = new ArrayList<>();
        int voucherItemNo = 1;
        shrVcrItems = shrVcrItems.stream()
                .filter(ShrVcrItem -> !CoreConstant.VOUCHER_STATUS_DELETED.equals(ShrVcrItem.getStatus()))
                .collect(Collectors.toList());
        for (ShrVcrItem shrVcrItem:shrVcrItems
             ) {
            CaseAccountInfo receiptVoucherCaseAccount = shrVcrItem.getCaseAccount();
            CaseAccountInfoDTO caseAccountInfoDTO = DataUtils.copyProperties(receiptVoucherCaseAccount, CaseAccountInfoDTO.class);
            
            SearchAccountLedgerDTO searchAccountLedgerDTO = new SearchAccountLedgerDTO();
            searchAccountLedgerDTO.setCaseAcId(caseAccountInfoDTO.getCaseAcId());
            searchAccountLedgerDTO.setAnalysisCode("681-000");
            searchAccountLedgerDTO.setControlAcId(0);
            List<AccountLedgerResultDTO> accountLedgerResultDTOList = shrGeneralLedgerService.searchGeneralLedgerListByCriteria(searchAccountLedgerDTO);
            BigDecimal balance = new BigDecimal(BigInteger.ZERO);
            if(accountLedgerResultDTOList != null){
                BigDecimal debit = new BigDecimal(BigInteger.ZERO);
                BigDecimal credit = new BigDecimal(BigInteger.ZERO);
                for (AccountLedgerResultDTO accountLedgerResultDTO:accountLedgerResultDTOList
                ) {
                    BigDecimal debit1 = accountLedgerResultDTO.getDebit();
                    BigDecimal credit1 = accountLedgerResultDTO.getCredit();
                    if(debit1 == null){
                        debit1 = new BigDecimal(BigInteger.ZERO);
                    }
                    if(credit1 == null){
                        credit1 = new BigDecimal(BigInteger.ZERO);
                    }

                    debit = debit.add(debit1);
                    credit = credit.add(credit1);
                }
                balance = credit.subtract(debit);
            }

            BigDecimal voucherAmount = shrVcrItem.getVoucherAmount();
            BigDecimal liquidCashAmount = new BigDecimal(BigInteger.ZERO);
            Boolean realizationFee1 = voucher.getRealizationFee();
            if(caseAccountInfoDTO.getLiquidCashAmount() != null){
                liquidCashAmount = caseAccountInfoDTO.getLiquidCashAmount();
            }
            voucherAmount = voucherAmount.add(liquidCashAmount);

            Integer caseFeeTypeId = null;
            if (caseAccountInfoDTO.getCaseAccountCode1().equals(ShroffConstant.VOUCHER_GROUP_CODE_BANKRUPTCY)) {
                caseFeeTypeId = 2;
            } else if (caseAccountInfoDTO.getCaseAccountCode1().equals(ShroffConstant.VOUCHER_GROUP_CODE_LIQUIDATION)) {
                caseFeeTypeId = 16;
            }
            CaseFeeMain caseFeeMain = null;
            if (null != caseFeeTypeId) {
                caseFeeMain = caseFeeMainRepository
                        .findOne(CaseFeeMainPredicate.findByCaseFeeTypeIdAndDate(caseFeeTypeId, DateUtils.getCurrentDate()));
            }
            BigDecimal realizationFee = new BigDecimal(BigInteger.ZERO);
            if(caseFeeMain != null){
                realizationFee = caseFeeMain.getFeeAmount();
            }
            
            if(balance.compareTo(new BigDecimal(BigInteger.ZERO)) <= 0 || (realizationFee1 != null && realizationFee1 && voucherAmount.compareTo(realizationFee) <= 0)){
                continue;
            }
            
            JournalVoucherAccountItemDTO debitItem = new JournalVoucherAccountItemDTO();
            JournalVoucherAccountItemDTO creditItem = new JournalVoucherAccountItemDTO();
            
            debitItem.setAccount(caseAccountInfoDTO);
            
            ControlAccountDTO controlAccountDTO = new ControlAccountDTO();
            if("B".equals(voucher.getGroupCode())){
                ShrCtlAc controlAccount = controlAccountRepository.findByCtlAcCodeAndStatus("B-C-00004/0000", "ACT");
                controlAccountDTO = DataUtils.copyProperties(controlAccount, ControlAccountDTO.class);
            }else if("L".equals(voucher.getGroupCode())){
                ShrCtlAc controlAccount = controlAccountRepository.findByCtlAcCodeAndStatus("L-C-00015/0000", "ACT");
                controlAccountDTO = DataUtils.copyProperties(controlAccount, ControlAccountDTO.class);
            }
            creditItem.setControlAccount(controlAccountDTO);
            
            debitItem.setVoucherItemNo(voucherItemNo);
            voucherItemNo++;
            creditItem.setVoucherItemNo(voucherItemNo);
            voucherItemNo++;
            
            if(realizationFee1 != null && realizationFee1){
                boolean chargeRealizationFee = false;
                chargeRealizationFee = totalLTminFee(caseAccountInfoDTO, DateUtils.getCurrentDate()) == 1 ? true : false;
                
                if(chargeRealizationFee){
                    voucherAmount = voucherAmount.subtract(realizationFee);
                }
            }
            
            if(voucherAmount != null && balance.compareTo(voucherAmount) < 0){
                debitItem.setAmountDr(balance);
                creditItem.setAmountCr(balance);
            }else{
                debitItem.setAmountDr(voucherAmount);
                creditItem.setAmountCr(voucherAmount);
            }
            
            debitItem.setCaseName(shrVcrItem.getCaseAccount().getCaseAccountName());
            creditItem.setCaseName(shrVcrItem.getCaseAccount().getCaseAccountName());

            AnalysisCode analysisCode = analysisCodeRepository.findByAnalysisCode("681-000");
            debitItem.setAnalysisCode(analysisCode.getAnalysisCode());
            creditItem.setAnalysisCode(analysisCode.getAnalysisCode());
            debitItem.setAnalysisCodeId(analysisCode.getAnalysisCodeId());
            creditItem.setAnalysisCodeId(analysisCode.getAnalysisCodeId());
            debitItem.setParticulars("REFUND TO COVER DEFICIT");
            creditItem.setParticulars("REFUND TO COVER DEFICIT FR " + caseAccountInfoDTO.getCaseAccountCode1() + caseAccountInfoDTO.getCaseAccountSeq() + "/" + caseAccountInfoDTO.getCaseAccountYear());
            
            debitItem.setStatus(shrVcrItem.getStatus());
            creditItem.setStatus(shrVcrItem.getStatus());

            debitItem.setCaseTypeCodeValue(caseAccountInfoDTO.getCaseAccountCode1());
            debitItem.setCaseYearValue(caseAccountInfoDTO.getCaseAccountYear().toString());
            debitItem.setAccountTypeCodeValue(caseAccountInfoDTO.getCaseAccountCode2());
            creditItem.setCaseTypeCodeValue(controlAccountDTO.getCtlAcCode1());
            creditItem.setCaseYearValue(controlAccountDTO.getCtlAcYear().toString());
            creditItem.setAccountTypeCodeValue(controlAccountDTO.getCtlAcCode2());

            String caseAccountSeqForDR = caseAccountInfoDTO.getCaseAccountSeq().toString();
            int len = 5 - caseAccountSeqForDR.length();
            StringBuilder stringBuilderForDR = new StringBuilder();
            for (int i = 0; i < len; i++) {
                stringBuilderForDR.append(0);
            }
            stringBuilderForDR.append(caseAccountSeqForDR);
            debitItem.setCaseNoValue(stringBuilderForDR.toString());

            String caseAccountSeqForCR = controlAccountDTO.getCtlAcCodeSeq().toString();
            int length = 5 - caseAccountSeqForCR.length();
            StringBuilder stringBuilderForCR = new StringBuilder();
            for (int i = 0; i < length; i++) {
                stringBuilderForCR.append(0);
            }
            stringBuilderForCR.append(caseAccountSeqForCR);
            creditItem.setCaseNoValue(stringBuilderForCR.toString());

            journalVoucherAccountItems.add(debitItem);
            journalVoucherAccountItems.add(creditItem);
        }
        log.info("getJournalVoucherAccountItems() end - journalVoucherAccountItems: " + journalVoucherAccountItems);
        return journalVoucherAccountItems;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer approvePaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("findPaymentVoucher() start - " + paymentVoucherDetail);
        preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
            WorkFlowAction.Approve.getCode());
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = paymentVoucherDetail.getPaymentVoucherBasicInformation();

        Long workflowId = null;
        if (paymentVoucherBasicInformation.getIsAutoPaymentVoucher() != null
            && paymentVoucherBasicInformation.getIsAutoPaymentVoucher()) {
            workflowId = voucherService.saveAutoWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
                paymentVoucherBasicInformation.getSysWorkFlowRule(), null);
        } else {
            workflowId = saveWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
                paymentVoucherBasicInformation.getSysWorkFlowRule(), null);
        }

        paymentVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = voucherService.savePaymentVoucherInside(paymentVoucherDetail);

        log.info("approvePaymentVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer rejectReceiptVoucher(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        log.info("rejectReceiptVoucher() start - ReceiptVoucherDetailDTO: " + receiptVoucherDetail);
        Set<String> notEnoughOnholdCredit = new HashSet<>();
        Set<String> ctlNotEnoughOnholdCredit = new HashSet<>();
        prepareReceiptVoucherWorkFlow(receiptVoucherDetail.getReceiptVoucherBasicInformation(),
            WorkFlowAction.Reject.getCode());
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo = receiptVoucherDetail.getReceiptVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(receiptVoucherBasicInfo.getWorkflowId(),
            receiptVoucherBasicInfo.getSysWorkFlowRule(), receiptVoucherDetail.getRejectRemark());

        receiptVoucherBasicInfo.setWorkflowId(workflowId);
        // receiptVoucherBasicInfo.setFirstApproverId(null);
        receiptVoucherBasicInfo.setPaymentVerifierId(null);

        Integer voucherId = this.saveReceiptVoucherInside(receiptVoucherDetail);

        List<ReceiptVoucherAccountItemDTO> receiptVoucherItems = receiptVoucherDetail.getReceiptVoucherAccountItems();
        Map<Integer, CaseAccountInfoDTO> caseAccountMap = new HashMap<Integer, CaseAccountInfoDTO>();
        Map<Integer, ControlAccountDTO> ctlAccountMap = new HashMap<>();
        for (ReceiptVoucherAccountItemDTO item : receiptVoucherItems) {
            if (item.getAccount() != null && item.getAccount().getCaseAcId() != null
                && item.getAccount().getCaseAcId() != 0) {
                Integer caseAcId = item.getAccount().getCaseAcId();
                CaseAccountInfoDTO caseAccount = null;
                if (caseAccountMap.containsKey(caseAcId)) {
                    caseAccount = caseAccountMap.get(caseAcId);
                } else {
                    caseAccount = item.getAccount();
                    caseAccountMap.put(caseAcId, caseAccount);
                }

                if (caseAccount.getOnHoldAmountCr().compareTo(item.getVoucherAmount()) < 0) {
                    notEnoughOnholdCredit.add(caseAccount.getCaseAcNumber());
                }
                caseAccount.setOnHoldAmountCr(caseAccount.getOnHoldAmountCr().subtract(item.getVoucherAmount()));
            } else if (item.getControlAccount() != null && item.getControlAccount().getCtlAcId() != null
                && item.getControlAccount().getCtlAcId() != 0) {
                Integer ctlAcId = item.getControlAccount().getCtlAcId();
                ControlAccountDTO account = null;
                if (ctlAccountMap.containsKey(ctlAcId)) {
                    account = ctlAccountMap.get(ctlAcId);
                } else {
                    account = item.getControlAccount();
                    ctlAccountMap.put(ctlAcId, account);
                }
                if (account.getOnHoldAmountCr().compareTo(item.getVoucherAmount()) < 0) {
                    ctlNotEnoughOnholdCredit.add(account.getCtlAcCode());
                }
                account.setOnHoldAmountCr(CommonUtils.subtract(account.getOnHoldAmountCr(), item.getVoucherAmount()));
            }
            decreaseRealizationFeeCrAndDr(receiptVoucherBasicInfo, ctlAccountMap, caseAccountMap, item);
        }
        saveCaseAccountInfos(caseAccountMap.values());
        saveControlAccountInfos(ctlAccountMap.values());
        log.info("rejectReceiptVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer rejectPaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("rejectPaymentVoucher() start - " + paymentVoucherDetail);
        preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
            WorkFlowAction.Reject.getCode());
        List<ErrorMsg> errorMsgs = new ArrayList<>();
        Set<String> notEnoughOnholdDebit = new HashSet<>();
        Set<String> ctlNotEnoughOnholdDebit = new HashSet<>();
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = paymentVoucherDetail.getPaymentVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
            paymentVoucherBasicInformation.getSysWorkFlowRule(), paymentVoucherDetail.getRejectRemark());

        paymentVoucherBasicInformation.setWorkflowId(workflowId);
        paymentVoucherBasicInformation.setPaymentVerifierId(null);
        Integer voucherId = savePaymentVoucherInside(paymentVoucherDetail);

        List<PaymentVoucherAccountItemDTO> paymentVoucherItems = paymentVoucherDetail.getPaymentVoucherItems();
        Map<Integer, CaseAccountInfoDTO> accountMap = new HashMap<>();
        Map<Integer, ControlAccountDTO> ctlAccountMap = new HashMap<>();
        for (PaymentVoucherAccountItemDTO item : paymentVoucherItems) {
            if (item.getAccount() != null && item.getAccount().getCaseAcId() != null
                && item.getAccount().getCaseAcId() != 0) {
                Integer caseAcId = item.getAccount().getCaseAcId();
                CaseAccountInfoDTO account = null;
                if (accountMap.containsKey(caseAcId)) {
                    account = accountMap.get(caseAcId);
                } else {
                    account = item.getAccount();
                    accountMap.put(caseAcId, account);
                }
                if (BigDecimal.ZERO.compareTo(item.getAmount()) <= 0) {
                    account.setOnHoldAmountDr(account.getOnHoldAmountDr().subtract(item.getAmount()));
                } else {
                    account
                        .setOnHoldAmountCr(CommonUtils.subtract(account.getOnHoldAmountCr(), item.getAmount().abs()));
                }
                if (account.getOnHoldAmountDr().compareTo(BigDecimal.ZERO) < 0) {
                    notEnoughOnholdDebit.add(account.getCaseAcNumber());
                }
            } else if (item.getControlAccount() != null && item.getControlAccount().getCtlAcId() != null
                && item.getControlAccount().getCtlAcId() != 0) {
                Integer ctlAcId = item.getControlAccount().getCtlAcId();
                ControlAccountDTO account = null;
                if (accountMap.containsKey(ctlAcId)) {
                    account = ctlAccountMap.get(ctlAcId);
                } else {
                    account = item.getControlAccount();
                    ctlAccountMap.put(ctlAcId, account);
                }
                if (BigDecimal.ZERO.compareTo(item.getAmount()) <= 0) {
                    account.setOnHoldAmountDr(account.getOnHoldAmountDr().subtract(item.getAmount()));
                } else {
                    account
                        .setOnHoldAmountCr(CommonUtils.subtract(account.getOnHoldAmountCr(), item.getAmount().abs()));
                }
                if (account.getOnHoldAmountDr().compareTo(BigDecimal.ZERO) < 0) {
                    ctlNotEnoughOnholdDebit.add(account.getCtlAcCode());
                }
            }
        }
        if (CommonUtils.isNotEmpty(notEnoughOnholdDebit)) {
            errorMsgs.add(new ErrorMsg(MsgCodeConstant.MSG_ONHOLD_DEBIT_NOT_ENOUGH, MsgParamCodeConstant.CASE_ACCOUNT,
                genCollectionStr(notEnoughOnholdDebit)));
        }
        if (CommonUtils.isNotEmpty(ctlNotEnoughOnholdDebit)) {
            errorMsgs.add(new ErrorMsg(MsgCodeConstant.MSG_ONHOLD_DEBIT_NOT_ENOUGH,
                MsgParamCodeConstant.CONTROL_ACCOUNT, genCollectionStr(ctlNotEnoughOnholdDebit)));
        }
        if (CommonUtils.isNotEmpty(errorMsgs)) {
            throw new BusinessException("Reject Payment Voucher Failed", errorMsgs);
        }
        saveCaseAccountInfos(accountMap.values());
        saveControlAccountInfos(ctlAccountMap.values());

        log.info("rejectPaymentVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer verifyReceiptVoucher(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        log.info("verifyReceiptVoucher() start - " + receiptVoucherDetail);
        prepareReceiptVoucherWorkFlow(receiptVoucherDetail.getReceiptVoucherBasicInformation(),
            WorkFlowAction.Verify.getCode());
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo = receiptVoucherDetail.getReceiptVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(receiptVoucherBasicInfo.getWorkflowId(),
            receiptVoucherBasicInfo.getSysWorkFlowRule(), null);
        
        ReceiptVoucherBasicInfoDTO dto = new ReceiptVoucherBasicInfoDTO();
        dto = receiptVoucherBasicInfo;

        dto.setWorkflowId(workflowId);
        dto.setPaymentVerifierId(RequestContextUtils.getCurrentUserInfo().getPostId());
        dto.setPaymentApproverName(RequestContextUtils.getCurrentUserInfo().getUserName());

        Integer voucherId = this.saveReceiptVoucherInside(receiptVoucherDetail);

        ControlAccountDTO controlAccount = dto.getControlAccount();
        controlAccount
            .setOnHoldAmountDr(controlAccount.getOnHoldAmountDr().add(dto.getVoucherTotalAmount()));
        controlAccountService.saveControlAccount(controlAccount);

        log.info("verifyReceiptVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer verifyPaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("verifyPaymentVoucher() start - " + paymentVoucherDetail);
        preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
            WorkFlowAction.Verify.getCode());
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = paymentVoucherDetail.getPaymentVoucherBasicInformation();

        Long workflowId = null;
        if (paymentVoucherBasicInformation.getIsAutoPaymentVoucher() != null
            && paymentVoucherBasicInformation.getIsAutoPaymentVoucher()) {
            workflowId = voucherService.saveAutoWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
                paymentVoucherBasicInformation.getSysWorkFlowRule(), null);
        } else {
            workflowId = saveWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
                paymentVoucherBasicInformation.getSysWorkFlowRule(), null);
        }

        paymentVoucherBasicInformation.setWorkflowId(workflowId);
        UserInfo currentUserInfo = RequestContextUtils.getCurrentUserInfo();
        if (currentUserInfo == null) {
            currentUserInfo = new UserInfo();
            Post userInfo = getUserInfo();
            currentUserInfo.setPostId(userInfo.getPostId());
            currentUserInfo.setDivisionId(userInfo.getDivision().getDivisionId());
            currentUserInfo.setUserName(userInfo.getPostTitle());
        }
        paymentVoucherBasicInformation.setPaymentVerifierId(currentUserInfo.getPostId());
        paymentVoucherBasicInformation.setPaymentApproverName(currentUserInfo.getUserName());
        Integer voucherId = voucherService.savePaymentVoucherInside(paymentVoucherDetail);

        if (paymentVoucherBasicInformation.getPaymentMethod().toUpperCase().trim().contains(CQ)) {
            ChequeDTO cheque = new ChequeDTO();
            cheque.setSource(ShroffConstant.CHEQUE_SOURCE_TYPE_MANUAL);
            cheque.setChequeTypeId(ShroffConstant.CQ_OUTGOING);
            cheque.setShrVcrInfo(DataUtils.copyProperties(paymentVoucherBasicInformation, VoucherDTO.class));
            cheque.setStatus(CoreConstant.CHEQUE_STATUS_DRAFT);

            ShrCheque shrCheque
                = chequeRepository.findOne(ChequePredicate.findOutgoingChequeForPaymentVoucher(voucherId, null));
            // the cheque is dividend cheque ,create by confirm dividend schedule
            if (shrCheque != null) {
                cheque = DataUtils.copyProperties(shrCheque, ChequeDTO.class);
            }

            cheque.setChequeAmount(paymentVoucherBasicInformation.getVoucherTotalAmount());
            cheque.setBankCode("");
            cheque.setChequeDate(paymentVoucherBasicInformation.getVoucherDate());
            cheque.setPayee(paymentVoucherBasicInformation.getPayeeName());

            if (CommonUtils.isBlank(cheque.getPayee())) {
                if (paymentVoucherBasicInformation.getComPayee() != null) {
                    cheque.setPayee(paymentVoucherBasicInformation.getComPayee().getPayeeName());
                }
            }
            Integer currencyId = paymentVoucherBasicInformation.getCurrencyId();
            if (currencyId != null) {
                CurrencyDTO currencyDTO = currencyService.findOne(currencyId);
                if (currencyDTO != null) {
                    cheque.setCurcy(currencyDTO);
                }
            }

            chequeService.saveOutgoingCheque(cheque);

        } else if (paymentVoucherBasicInformation.getPaymentMethod().toUpperCase().trim().contains(BTF)) {
            BankTransferItemDTO transfer = new BankTransferItemDTO();
            transfer.setTransferAmount(paymentVoucherBasicInformation.getVoucherTotalAmount());
        } else if (paymentVoucherBasicInformation.getPaymentMethod().toUpperCase().trim()
            .contains(ShroffConstant.VCR_PAYMENT_METHOD_AUTO_PAY)) {
            ShrAutoPayItem autoPayItem = new ShrAutoPayItem();
            autoPayItem.setVoucher(DataUtils.copyProperties(paymentVoucherBasicInformation, Voucher.class));
            autoPayItem.setStatus(CoreConstant.COMMON_STATUS_DRAFT);
            shrAutoPayItemRepository.save(autoPayItem);

        }

        ControlAccountDTO controlAccount = paymentVoucherBasicInformation.getControlAccount();
        BigDecimal voucherTotalAmount = paymentVoucherBasicInformation.getVoucherTotalAmount();
        controlAccount.setOnHoldAmountCr(controlAccount.getOnHoldAmountCr().add(voucherTotalAmount));

        controlAccountService.saveControlAccount(controlAccount);
        log.info("verifyPaymentVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer reverseReceiptVoucher(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        log.info("reverseReceiptVoucher() start - ReceiptVoucherDetailDTO: " + receiptVoucherDetail);
        prepareReceiptVoucherWorkFlow(receiptVoucherDetail.getReceiptVoucherBasicInformation(),
            WorkFlowAction.Reverse.getCode());
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo = receiptVoucherDetail.getReceiptVoucherBasicInformation();
        Long workflowId = saveWorkFlowHistory(receiptVoucherBasicInfo.getWorkflowId(),
            receiptVoucherBasicInfo.getSysWorkFlowRule(), receiptVoucherDetail.getRejectRemark());
        ControlAccountDTO controlAccount = receiptVoucherBasicInfo.getControlAccount();
        receiptVoucherBasicInfo.setWorkflowId(workflowId);
        Integer voucherId = this.saveReceiptVoucherInside(receiptVoucherDetail);
        // ShroffConstant.POOL_
        // RELATED_D, receiptVoucherBasicInfo.getCurrencyId());
        Map<Integer, ControlAccountDTO> cas = new HashMap<>();
        Map<Integer, CaseAccountInfoDTO> cais = new HashMap<>();
        if (controlAccount != null) {
            controlAccount.setOnHoldAmountDr(CommonUtils.subtract(controlAccount.getOnHoldAmountDr(),
                receiptVoucherBasicInfo.getVoucherTotalAmount()));
            cas.put(controlAccount.getCtlAcId(), controlAccount);
        }
        for (ReceiptVoucherAccountItemDTO item : receiptVoucherDetail.getReceiptVoucherAccountItems()) {
            if (item.getAccount() != null) {
                CaseAccountInfoDTO caseAccountInfoDTO = null;
                if (!cais.containsKey(item.getAccount().getCaseAcId())) {
                    caseAccountInfoDTO = item.getAccount();
                    cais.put(caseAccountInfoDTO.getCaseAcId(), caseAccountInfoDTO);
                } else {
                    caseAccountInfoDTO = cais.get(item.getAccount().getCaseAcId());
                }
                caseAccountInfoDTO.setOnHoldAmountCr(
                    CommonUtils.subtract(caseAccountInfoDTO.getOnHoldAmountCr(), item.getVoucherAmount()));
            } else if (item.getControlAccount() != null) {
                ControlAccountDTO controlAccountDTO = null;
                if (!cas.containsKey(item.getControlAccount().getCtlAcId())) {
                    controlAccountDTO = item.getControlAccount();
                    cas.put(controlAccountDTO.getCtlAcId(), controlAccountDTO);
                } else {
                    controlAccountDTO = cas.get(item.getControlAccount().getCtlAcId());
                }
                controlAccountDTO.setOnHoldAmountCr(
                    CommonUtils.subtract(controlAccountDTO.getOnHoldAmountCr(), item.getVoucherAmount()));
            }
            decreaseRealizationFeeCrAndDr(receiptVoucherBasicInfo, cas, cais, item);
        }
        this.saveCaseAccountInfos(cais.values());
        this.saveControlAccountInfos(cas.values());
        log.info("reverseReceiptVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    private void decreaseRealizationFeeCrAndDr(ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo,
        Map<Integer, ControlAccountDTO> cas, Map<Integer, CaseAccountInfoDTO> caseAccountMap,
        ReceiptVoucherAccountItemDTO item) {
        log.info("decreaseRealizationFeeCrAndDr() start - receiptVoucherBasicInfo = " + receiptVoucherBasicInfo
            + ", cas" + cas + ", item" + item);
        if (item.getAccount() != null && receiptVoucherBasicInfo.getRealizationFee() != null
            && receiptVoucherBasicInfo.getRealizationFee()) {
            Map<String, Object> map
                = handleRealizationFee(DateUtils.getCurrentDate(), cas, item.getAccount(), item.getAnalysisCode());
            Boolean chargeRealizationFee = (Boolean)map.get("chargeRealizationFee");
            if (chargeRealizationFee) {
                BigDecimal realizationFee = (BigDecimal)map.get("realizationFee");
                ControlAccountDTO controlAccountDTO = (ControlAccountDTO)map.get("controlAccountDTO");
                if (cas.containsKey(controlAccountDTO.getCtlAcId())) {
                    controlAccountDTO = cas.get(controlAccountDTO.getCtlAcId());
                }
                controlAccountDTO
                    .setOnHoldAmountCr(CommonUtils.subtract(controlAccountDTO.getOnHoldAmountCr(), realizationFee));

                CaseAccountInfoDTO account = item.getAccount();
                if (caseAccountMap.containsKey(account.getCaseAcId())) {
                    account = caseAccountMap.get(account.getCaseAcId());
                }
                account.setOnHoldAmountDr(CommonUtils.subtract(account.getOnHoldAmountDr(), realizationFee));
            }
        }
        log.info("decreaseRealizationFeeCrAndDr() end");
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer reversePaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("reversePaymentVoucher() start - " + paymentVoucherDetail);
        preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
            WorkFlowAction.Reverse.getCode());
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = paymentVoucherDetail.getPaymentVoucherBasicInformation();
        // paymentVoucherBasicInformation
        Long workflowId = saveWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
            paymentVoucherBasicInformation.getSysWorkFlowRule(), paymentVoucherDetail.getRejectRemark());

        paymentVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = savePaymentVoucherInside(paymentVoucherDetail);
        // cancel cheque
        ShrCheque shrCheque
            = chequeRepository.findOne(ChequePredicate.findOutgoingChequeForPaymentVoucher(voucherId, null));
        if (shrCheque != null) {
            shrCheque.setStatus(CoreConstant.CHEQUE_STATUS_CANCELLED);
            chequeRepository.save(shrCheque);
        }

        // handle autopay voucher
        boolean autoPay
            = ShroffConstant.VCR_PAYMENT_METHOD_AUTO_PAY.equals(paymentVoucherBasicInformation.getPaymentMethod());
        if (autoPay) {
            Voucher voucher = voucherRepository.findOne(paymentVoucherBasicInformation.getVoucherId());
            List<ShrAutoPayItem> autoPayItems = shrAutoPayItemRepository.findByVoucher(voucher);
            if (CommonUtils.isNotEmpty(autoPayItems)) {
                for (ShrAutoPayItem autoPayItem : autoPayItems) {
                    autoPayItem.setStatus(ShroffConstant.AUTO_PAY_ITEM_STATUS_CANCELLED);
                    shrAutoPayItemRepository.save(autoPayItem);
                }
            }
        }

        ControlAccountDTO controlAccount = paymentVoucherBasicInformation.getControlAccount();

        // sub payment voucher account cr and dr
        Map<Integer, ControlAccountDTO> cas = new HashMap<>();
        Map<Integer, CaseAccountInfoDTO> cais = new HashMap<>();
        if (controlAccount != null) {
            controlAccount.setOnHoldAmountCr(CommonUtils.subtract(controlAccount.getOnHoldAmountCr(),
                paymentVoucherBasicInformation.getVoucherTotalAmount()));
            cas.put(controlAccount.getCtlAcId(), controlAccount);
        }
        for (PaymentVoucherAccountItemDTO item : paymentVoucherDetail.getPaymentVoucherItems()) {
            if (item.getAccount() != null) {
                CaseAccountInfoDTO caseAccountInfoDTO = null;
                if (!cais.containsKey(item.getAccount().getCaseAcId())) {
                    caseAccountInfoDTO = item.getAccount();
                    cais.put(caseAccountInfoDTO.getCaseAcId(), caseAccountInfoDTO);
                } else {
                    caseAccountInfoDTO = cais.get(item.getAccount().getCaseAcId());
                }
                if (BigDecimal.ZERO.compareTo(item.getAmount()) < 0) {
                    caseAccountInfoDTO.setOnHoldAmountDr(
                        CommonUtils.subtract(caseAccountInfoDTO.getOnHoldAmountDr(), item.getAmount()));
                } else {
                    caseAccountInfoDTO.setOnHoldAmountCr(
                        CommonUtils.subtract(caseAccountInfoDTO.getOnHoldAmountCr(), item.getAmount().abs()));
                }
            } else if (item.getControlAccount() != null) {
                ControlAccountDTO controlAccountDTO = null;
                if (!cas.containsKey(item.getControlAccount().getCtlAcId())) {
                    controlAccountDTO = item.getControlAccount();
                    cas.put(controlAccountDTO.getCtlAcId(), controlAccountDTO);
                } else {
                    controlAccountDTO = cas.get(item.getControlAccount().getCtlAcId());
                }
                if (BigDecimal.ZERO.compareTo(item.getAmount()) < 0) {
                    controlAccountDTO.setOnHoldAmountDr(
                        CommonUtils.subtract(controlAccountDTO.getOnHoldAmountDr(), item.getAmount()));
                } else {
                    controlAccountDTO.setOnHoldAmountCr(
                        CommonUtils.subtract(controlAccountDTO.getOnHoldAmountCr(), item.getAmount().abs()));
                }
            }
        }
        this.saveCaseAccountInfos(cais.values());
        this.saveControlAccountInfos(cas.values());

        log.info("reversePaymentVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer deleteReceiptVoucher(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        log.info("deleteReceiptVoucher() start - ReceiptVoucherDetailDTO: " + receiptVoucherDetail);
        prepareReceiptVoucherWorkFlow(receiptVoucherDetail.getReceiptVoucherBasicInformation(),
            WorkFlowAction.Delete.getCode());
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo = receiptVoucherDetail.getReceiptVoucherBasicInformation();
        Long workflowId = saveWorkFlowHistory(receiptVoucherBasicInfo.getWorkflowId(),
            receiptVoucherBasicInfo.getSysWorkFlowRule(), null);

        receiptVoucherBasicInfo.setWorkflowId(workflowId);
        Integer voucherId = this.saveReceiptVoucherInside(receiptVoucherDetail);

        log.info("deleteReceiptVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer submitAndUpliftPaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("submitAndUpliftPaymentVoucher start - paymentVoucherDetail: " + paymentVoucherDetail);
        Integer voucherId = submitPaymentVoucher(paymentVoucherDetail, true);
        PendingMoneyDTO pendingMoney = paymentVoucherDetail.getPendingMoney();
        if (pendingMoney != null) {
            pendingMoney.setPaymentVoucherId(voucherId);
            Integer pendingMoneyId = this.savePendingMoney(pendingMoney, CoreConstant.RESERVE_MONEY_DRAFT);
            if (ShroffConstant.PENDING_MONEY_TYPE_UPLIFT.equals(pendingMoney.getPendingMoneyType())) {
                /*
                 * if (pendingMoney.getAmount().compareTo(ShroffConstant.HUNDRED_THOUSAND) < 0) {
                 * PaymentVoucherDetailDTO detailDTO = this.findPaymentVoucher(voucherId);
                 * this.secondApproveWithPendingPaymentVoucher(detailDTO); ShrPendingMoney shrPendingMoney =
                 * pendingMoneyRepository.findOne(pendingMoneyId); CaseAccountReverseMoney caseAccountReverseMoney =
                 * shrPendingMoney.getCaseAccountReverseMoney(); if (caseAccountReverseMoney != null) {
                 * CaseAccountReverseMoneyDTO reverseMoneyDTO = DataUtils.copyProperties(caseAccountReverseMoney,
                 * CaseAccountReverseMoneyDTO.class);
                 * reverseMoneyDTO.setFundsAvailableDate(reverseMoneyDTO.getTargetCompleteDate());
                 * rserverMoneyService.saveCaseAccountReverseMoney(reverseMoneyDTO); } }
                 */
            }
        }
        log.info("submitPaymentVoucher end - " + voucherId);
        return voucherId;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer firstApproveWithPendingPaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("firstApproveWithPendingPaymentVoucher start - paymentVoucherDetail: " + paymentVoucherDetail);
        preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
            WorkFlowAction.FirstApproveWithPending.getCode());
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = paymentVoucherDetail.getPaymentVoucherBasicInformation();
        Long workflowId = saveWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
            paymentVoucherBasicInformation.getSysWorkFlowRule(), null);
        paymentVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = savePaymentVoucherInside(paymentVoucherDetail);
        log.info("firstApproveWithPendingPaymentVoucher end - " + voucherId);
        return voucherId;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer secondApproveWithPendingPaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("secondApproveWithPendingPaymentVoucher start - paymentVoucherDetail: " + paymentVoucherDetail);
        preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
            WorkFlowAction.SecondApproveWithPending.getCode());
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = paymentVoucherDetail.getPaymentVoucherBasicInformation();
        Long workflowId = saveWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
            paymentVoucherBasicInformation.getSysWorkFlowRule(), null);
        paymentVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = savePaymentVoucherInside(paymentVoucherDetail);
        this.savePendingMoney(paymentVoucherDetail.getPendingMoney(),
            CoreConstant.RESERVE_MONEY_WAITING_FOR_ALLOCATION);
        log.info("approvePaymentVoucher() end - VoucherId: " + voucherId);
        return voucherId;

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer deletePendingMoney(PendingMoneyDTO pendingMoney) {
        log.info("deletePendingMoney start - pendingMoney: " + pendingMoney);
        pendingMoney.setStatus(CoreConstant.STATUS_DELETE);
        Integer moneyId = this.savePendingMoney(pendingMoney, CoreConstant.STATUS_DELETE);
        log.info("deletePendingMoney end - " + moneyId);
        return moneyId;
    }

    private Integer savePendingMoney(PendingMoneyDTO pendingMoneyDTO, String moneyStatus) {
        log.info("savePendingMoney start - pendingMoneyDTO: " + pendingMoneyDTO);
        Integer pendingMoneyId = null;
        if (pendingMoneyDTO != null) {
            ShrPendingMoney pendingMoney = DataUtils.copyProperties(pendingMoneyDTO, ShrPendingMoney.class);
            String accountNumber = null;
            CaseAccountInfoDTO caseAccountInfoDTO = null;
            if (CommonUtils.isNotBlank(pendingMoneyDTO.getCaseAccountCode1())
                && CommonUtils.isNotBlank(pendingMoneyDTO.getCaseAccountCode2())
                && pendingMoneyDTO.getCaseAccountSeq() != null && pendingMoneyDTO.getCaseAccountYear() != null) {
                accountNumber = BusinessUtils.genAccountNumber(pendingMoneyDTO.getCaseAccountCode1(),
                    pendingMoneyDTO.getCaseAccountCode2(), pendingMoneyDTO.getCaseAccountSeq(),
                    pendingMoneyDTO.getCaseAccountYear());
            }
            if (CommonUtils.isNotBlank(accountNumber)) {
                caseAccountInfoDTO = caseAccountService.findByAccountNumber(accountNumber);
            }
            if (caseAccountInfoDTO == null) {
                throw new BusinessException();
            }
            if (ShroffConstant.PENDING_MONEY_TYPE_UPLIFT.equals(pendingMoneyDTO.getPendingMoneyType())) {
                CaseAccountReverseMoneyDTO moneyDTO = pendingMoneyDTO.getCaseAccountReverseMoney();
                if (moneyDTO == null || moneyDTO.getCaseAccountReverseMoneyId() == null) {
                    moneyDTO = new CaseAccountReverseMoneyDTO();
                    Date requestDate = new Date();
                    if (requestDate.after(DateUtils.get4PMHalfDate())) {
                        requestDate = commonService.addWorkingDays(requestDate, 1);
                    }
                    moneyDTO.setRequestDate(requestDate);
                }
                moneyDTO.setUpliftAmount(pendingMoneyDTO.getAmount());
                moneyDTO.setTargetCompleteDate(pendingMoneyDTO.getTargetCompleteDate());
                moneyDTO.setCaseAccountNumber(accountNumber);
                moneyDTO.setCaseAccountNumber(caseAccountInfoDTO.getCaseAcNumber());
                moneyDTO.setCaseAccountInfo(caseAccountInfoDTO);
                moneyDTO.setCaseAccountName(caseAccountInfoDTO.getCaseAccountName());
                moneyDTO.setStatus(moneyStatus);
                CaseAccountReverseMoney reverseMoney
                    = DataUtils.copyProperties(moneyDTO, CaseAccountReverseMoney.class);
                Integer moneyId = null;
                reverseMoney = this.rserverMoneyServiceRepository.save(reverseMoney);
                if (reverseMoney != null) {
                    moneyId = reverseMoney.getCaseAccountReverseMoneyId();
                }
                if (moneyId != null) {
                    pendingMoney.setCaseAccountReverseMoney(rserverMoneyServiceRepository.findOne(moneyId));
                    // pendingMoney.setJournalVoucherId(null);
                }
                // borrow money useless
            } else if (ShroffConstant.PENDING_MONEY_TYPE_BORROW_MONEY.equals(pendingMoneyDTO.getPendingMoneyType())) {

                ApplParamDTO analysisCodeParam
                    = applParamService.findByParamCode(ShroffConstant.VCR_BROMONEY_ANACODEID);
                ApplParamDTO accountParamDTO = null;
                if (ShroffConstant.VOUCHER_GROUP_CODE_LIQUIDATION.equals(pendingMoneyDTO.getCaseAccountCode1())) {
                    accountParamDTO = applParamService.findByParamCode(ShroffConstant.VCR_BROMONEY_L_CONTROLACID);
                } else if (ShroffConstant.VOUCHER_GROUP_CODE_BANKRUPTCY.equals(pendingMoneyDTO.getCaseAccountCode1())) {
                    accountParamDTO = applParamService.findByParamCode(ShroffConstant.VCR_BROMONEY_B_CONTROLACID);
                }
                AnalysisCodeDTO analysisCode = null;
                ShrCtlAc controlAccount = null;
                CaseAccountInfo caseAccountInfo = null;
                if (analysisCodeParam != null && accountParamDTO != null) {
                    analysisCode = analysisCodeService.findOne(Integer.valueOf(analysisCodeParam.getApplParamValue()));
                    controlAccount
                        = controlAccountRepository.findOne(Integer.valueOf(accountParamDTO.getApplParamValue()));
                    caseAccountInfo = caseAccountInfoRepository.findOne(caseAccountInfoDTO.getCaseAcId());
                } else {
                    throw new BusinessException();
                }

                Voucher journalVoucher = null;
                ShrVcrItem debitItem = null;
                ShrVcrItem creditItem = null;
                if (pendingMoneyDTO.getJournalVoucherId() != null) {
                    journalVoucher = voucherRepository.findOne(pendingMoneyDTO.getJournalVoucherId());
                    if (journalVoucher == null) {
                        throw new BusinessException();
                    }
                    journalVoucher.setGroupCode(pendingMoneyDTO.getCaseAccountCode1());
                    journalVoucher.setVoucherTotalAmount(pendingMoneyDTO.getAmount());
                    List<ShrVcrItem> vcrItems = shrVcrItemRepository.findByVoucherId(journalVoucher.getVoucherId());
                    if (vcrItems.size() != 2) {
                        throw new BusinessException();
                    }
                    for (ShrVcrItem shrVcrItem : vcrItems) {
                        if (ShroffConstant.CR.equals(shrVcrItem.getDebitCredit())) {
                            creditItem = shrVcrItem;
                            creditItem.setVoucherAmount(pendingMoneyDTO.getAmount());
                            creditItem.setCaseAccount(caseAccountInfo);
                            continue;
                        }
                        if (ShroffConstant.DR.equals(shrVcrItem.getDebitCredit())) {
                            debitItem = shrVcrItem;
                            debitItem.setVoucherAmount(pendingMoneyDTO.getAmount());
                            debitItem.setControlAccount(controlAccount);
                        }
                    }
                } else {
                    journalVoucher = createAutoVoucher(ShroffConstant.VT_JOU, pendingMoneyDTO.getAmount(),
                        ShroffConstant.POOL_RELATED_FOR_DETAIL_LEDGER, pendingMoneyDTO.getCaseAccountCode1());
                    debitItem = createAutoVoucherItem(pendingMoneyDTO.getAmount(), null, controlAccount,
                        ShroffConstant.DR, analysisCode.getAnalysisCodeId(), analysisCode.getAnalysisCode(), 1);
                    creditItem = createAutoVoucherItem(pendingMoneyDTO.getAmount(), caseAccountInfo, null,
                        ShroffConstant.CR, analysisCode.getAnalysisCodeId(), analysisCode.getAnalysisCode(), 2);
                }
                Integer voucherId;
                journalVoucher = voucherRepository.save(journalVoucher);
                if (journalVoucher != null && creditItem != null && debitItem != null) {
                    voucherId = journalVoucher.getVoucherId();
                    creditItem.setVoucherId(voucherId);
                    debitItem.setVoucherId(voucherId);
                    shrVcrItemRepository.save(creditItem);
                    shrVcrItemRepository.save(debitItem);
                    pendingMoney.setJournalVoucherId(voucherId);
                } else {
                    throw new BusinessException();
                }
            }
            pendingMoney = pendingMoneyRepository.save(pendingMoney);
            if (pendingMoney != null) {
                pendingMoneyId = pendingMoney.getPendingMoneyId();
            }
        }
        log.info("savePendingMoney end - " + pendingMoneyId);
        return pendingMoneyId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer deletePaymentVoucher(PaymentVoucherDetailDTO paymentVoucherDetail) {
        log.info("deletePaymentVoucher() start - " + paymentVoucherDetail);
        preparePaymentVoucherWorkFlow(paymentVoucherDetail.getPaymentVoucherBasicInformation(),
            WorkFlowAction.Delete.getCode());
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformation
            = paymentVoucherDetail.getPaymentVoucherBasicInformation();

        Long workflowId = saveWorkFlowHistory(paymentVoucherBasicInformation.getWorkflowId(),
            paymentVoucherBasicInformation.getSysWorkFlowRule(), null);

        paymentVoucherBasicInformation.setWorkflowId(workflowId);
        Integer voucherId = savePaymentVoucherInside(paymentVoucherDetail);

        log.info("deletePaymentVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    private void prepareReceiptVoucherWorkFlow(ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfoDTO, String action) {
        log.info("prepareReceiptVoucherWorkFlow() start -  status : " + receiptVoucherBasicInfoDTO.getStatus()
            + ", action : " + action);
        initReceiptVoucherWorkFlowRuleList();
        SysWorkFlowRuleDTO after = receiptVoucherWorkFlowRuleList.stream()
            .filter(item -> item.getAction().getCodeValue().equals(action)
                && item.getBeforeStatus().getCodeValue().equals(receiptVoucherBasicInfoDTO.getStatus()))
            .findFirst().orElse(null);
        if (after != null) {
            receiptVoucherBasicInfoDTO.setStatus(after.getAfterStatus().getCodeValue());
            receiptVoucherBasicInfoDTO.setSysWorkFlowRule(after);
        }
        log.info("prepareReceiptVoucherWorkFlow end -  after " + after);
    }

    private void preparePaymentVoucherWorkFlow(PaymentVoucherBasicInformationDTO paymentVoucherBasicInformationDTO,
        String action) {
        log.info("preparePaymentVoucherWorkFlow() start -  status : " + paymentVoucherBasicInformationDTO.getStatus()
            + ", action : " + action);
        initPaymentVoucherWorkFlowRuleList();
        SysWorkFlowRuleDTO after = paymentVoucherWorkFlowRuleList.stream()
            .filter(item -> item.getAction().getCodeValue().equals(action)
                && item.getBeforeStatus().getCodeValue().equals(paymentVoucherBasicInformationDTO.getStatus()))
            .findFirst().orElse(null);
        if (after != null) {
            paymentVoucherBasicInformationDTO.setStatus(after.getAfterStatus().getCodeValue());
            paymentVoucherBasicInformationDTO.setSysWorkFlowRule(after);
        }
        if (paymentVoucherBasicInformationDTO.getSourceCode() != null
            && ShroffConstant.CHEQUE_SOURCE_TYPE_DIVIDEND.equals(paymentVoucherBasicInformationDTO.getSourceCode())
            && WorkFlowAction.Reverse.getCode().equals(action)) {
            paymentVoucherBasicInformationDTO.setStatus(action);
        }
        log.info("preparePaymentVoucherWorkFlow end -  after " + after);
    }

    private void prepareJournalVoucherWorkFlow(JournalVoucherBasicInformationDTO journalVoucherBasicInformationDTO,
        String action) {
        log.info("prepareJournalVoucherWorkFlow() start -  status : " + journalVoucherBasicInformationDTO.getStatus()
            + ", action : " + action);
        initJournalVoucherWorkFlowRuleList();
        SysWorkFlowRuleDTO after = journalVoucherWorkFlowRuleList.stream()
            .filter(item -> item.getAction().getCodeValue().equals(action)
                && item.getBeforeStatus().getCodeValue().equals(journalVoucherBasicInformationDTO.getStatus()))
            .findFirst().orElse(null);
        if (after != null) {
            journalVoucherBasicInformationDTO.setStatus(after.getAfterStatus().getCodeValue());
            journalVoucherBasicInformationDTO.setSysWorkFlowRule(after);
        }
        log.info("prepareJournalVoucherWorkFlow end -  after " + after);
    }

    private String genCollectionStr(Collection<String> str) {
        StringBuilder sb = new StringBuilder(" ");
        str.forEach(item -> {
            sb.append(item).append(", ");
        });

        if (sb.length() > 2) {
            sb.delete(sb.length() - 2, sb.length());
            sb.append(" : ");
        }
        return sb.toString();
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveControlAccountInfos(Collection<ControlAccountDTO> collection) {
        if (CommonUtils.isNotEmpty(collection)) {
            collection.stream().forEach(account -> {
                log.info(">>>>>>>>>" + account);
                controlAccountService.saveControlAccount(account);
            });
        }
    }

    @Transactional(readOnly = true)
    @Override
    public List<VoucherDetailDTO> findVoucherForBatchJob(Date searchDate) {
        log.info("findVoucherForBatchJob() start - searchDate = " + searchDate);
        List<VoucherDetailDTO> result = null;
        if (searchDate != null) {
            List<Voucher> list
                = (List<Voucher>)voucherRepository.findAll(VoucherPredicate.findVoucherForBatchJob(searchDate));
            if (CommonUtils.isNotEmpty(list)) {
                result = new ArrayList<>();
                for (Voucher temp : list) {
                    VoucherDetailDTO dto = null;
                    List<ShrVcrItem> shrVcrItems = shrVcrItemRepository.findAvailableVoucherItem(temp.getVoucherId());
                    if (CommonUtils.isNotEmpty(shrVcrItems)) {
                        dto = voucherDetailDTOAssembler.toDTO(temp, shrVcrItems);
                        result.add(dto);
                    }
                }
            }
        }
        log.info("findVoucherForBatchJob() end and returnVal = " + result);
        return result;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void handleVoucherBatchJob(VoucherDetailDTO voucherDetail) {
        log.info("handleVoucherBatchJob start ");
        Voucher tmpVoucher = voucherRepository.findOne(voucherDetail.getVoucherDTO().getVoucherId());
        if (CoreConstant.VOUCHER_STATUS_POSTED.equals(tmpVoucher.getStatus())) {
            return;
        }
        voucherDetail = voucherDetailDTOAssembler.toDTO(tmpVoucher, tmpVoucher.getShrVcrItems());
        Calendar calendar = Calendar.getInstance();
        calendar.set(2019, Calendar.MARCH, 31, 23, 59, 59);
        calendar.set(Calendar.MILLISECOND, 999);
        Date tempDate = calendar.getTime();
        Date businessDate = DateUtils.addDays(new Date(), Integer.valueOf(sysParamService.getBusinessOffset()));
        String voucherType = voucherDetail.getVoucherDTO().getVoucherType().getVoucherTypeCode();
        VoucherDTO voucher = voucherDetail.getVoucherDTO();
        String paymentMethod = voucher.getPaymentMethod();
        if (voucher.getCreateDate().compareTo(tempDate) != 0) {
            /**
             * payment method is autopay, should check Whether the AUTOPAY instruction is already accepted by HSBCNet,
             * if not , voucher should not be allowed to post
             */
            boolean autoPay = voucherType.equals(ShroffConstant.VT_PAY) && CommonUtils.isNotBlank(paymentMethod)
                && paymentMethod.equals(ShroffConstant.VCR_PAYMENT_METHOD_AUTO_PAY);
            List<ShrAutoPayItem> autoPayItems = null;
            if (autoPay) {
                autoPayItems = shrAutoPayItemRepository.findByVoucher(tmpVoucher);
                if (CommonUtils.isNotEmpty(autoPayItems)) {
                    for (ShrAutoPayItem autoPayItem : autoPayItems) {
                        if (autoPayItem != null
                            && (autoPayItem.getStatus().equals(ShroffConstant.AUTO_PAY_ITEM_STATUS_GENERATED)
                                || autoPayItem.getStatus().equals(ShroffConstant.AUTO_PAY_ITEM_STATUS_DRAFT))) {
                            return;
                        }
                        if (autoPayItem != null
                            && autoPayItem.getStatus().equals(ShroffConstant.AUTO_PAY_ITEM_STATUS_EXECUTING)) {
                            autoPayItem.setStatus(ShroffConstant.AUTO_PAY_ITEM_STATUS_DONE);
                            shrAutoPayItemRepository.save(autoPayItem);
                            break;
                        }
                    }
                }
            }

            String status = voucher.getStatus();
            if (ShroffConstant.VCR_STATUS_VERIFIED.equals(status)) {
                voucher.setPostingDate(businessDate);
                voucher.setStatus(CoreConstant.VOUCHER_STATUS_POSTED);
            }
            Voucher voucherVal = DataUtils.copyProperties(voucher, Voucher.class);
            CommonPayeeDTO comPayee = voucher.getComPayee();
            if (comPayee != null && comPayee.getPayeeId() != null) {
                ShrComPayeeMain shrComPayeeMain = comPayeeRepository.findOne(comPayee.getPayeeId());
                voucherVal.setComPayee(shrComPayeeMain);
            } else {
                voucherVal.setComPayee(null);
            }
            voucherRepository.save(voucherVal);

            List<VoucherItemDTO> itemList = voucherDetail.getVoucherItems();
            itemList = itemList.stream()
                .filter(voucherItemDTO -> !CoreConstant.VOUCHER_STATUS_DELETED.equals(voucherItemDTO.getStatus()))
                .collect(Collectors.toList());
            if (CommonUtils.isNotEmpty(itemList)) {
                switch (voucherType) {
                    case ShroffConstant.VT_JOU:
                        handleJournalVoucher(voucher, itemList);
                        break;

                    case ShroffConstant.VT_PAY:
                        handlePaymentVoucher(voucher, itemList);
                        break;

                    case ShroffConstant.VT_REC:
                        handleReceiptVoucher(voucher, itemList);
                        break;

                    default:
                        break;
                }
            }
        }
        log.info("handleVoucherBatchJob end ");
    }

    // Journal Voucher
    private void handleJournalVoucher(VoucherDTO voucher, List<VoucherItemDTO> itemList) {
        log.info("handleJournalVoucher() start - " + voucher);
        Map<Integer, CaseAccountInfoDTO> accountMap = new HashMap<>();
        Map<Integer, ControlAccountDTO> ctlAcMap = new HashMap<>();
        Integer poolCaseAccId = voucher == null ? null : voucher.getPoolCaseAccId();
        Date businessDate = DateUtils.addDays(new Date(), Integer.valueOf(sysParamService.getBusinessOffset()));
        String analysisCode = null;
        for (VoucherItemDTO item : itemList) {
            BigDecimal debit = null;
            BigDecimal credit = null;
            if (item.getCaseAccount() != null && item.getCaseAccount().getCaseAcId() != null
                && item.getCaseAccount().getCaseAcId() != 0) {
                Integer caseAcId = item.getCaseAccount().getCaseAcId();
                CaseAccountInfoDTO account = null;
                if (accountMap.containsKey(caseAcId)) {
                    account = accountMap.get(caseAcId);
                } else {
                    account = item.getCaseAccount();
                    accountMap.put(caseAcId, account);
                }
                if (CommonUtils.isNotBlank(item.getDebitCredit())) {
                    if (ShroffConstant.CR.equals(item.getDebitCredit())) {
                        account.setLiquidCashAmount(account.getLiquidCashAmount().add(item.getVoucherAmount()));
                        credit = item.getVoucherAmount();
                    } else if (ShroffConstant.DR.equals(item.getDebitCredit())) {
                        account.setLiquidCashAmount(account.getLiquidCashAmount().subtract(item.getVoucherAmount()));
                        debit = item.getVoucherAmount();
                    }
                    shrGeneralLedgerService.saveGeneralLedgerForBatchJob(item.getAnalysisCode(), voucher.getVoucherId(),
                        credit, debit, account.getLiquidCashAmount(), caseAcId, null, item.getNature(),
                        voucher.getPoolRelated(), voucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                }
            } else if (item.getControlAccount() != null && item.getControlAccount().getCtlAcId() != null
                && item.getControlAccount().getCtlAcId() != 0) {
                Integer ctlAcId = item.getControlAccount().getCtlAcId();
                ControlAccountDTO ctlAc = null;
                if (ctlAcMap.containsKey(ctlAcId)) {
                    ctlAc = ctlAcMap.get(ctlAcId);
                } else {
                    ctlAc = item.getControlAccount();
                    ctlAcMap.put(ctlAcId, ctlAc);
                }
                if (CommonUtils.isNotBlank(item.getDebitCredit())) {
                    if (ShroffConstant.CR.equals(item.getDebitCredit())) {
                        if (ShroffConstant.CTL_AC_NATURE_AST.equals(ctlAc.getBalanceNature())) {
                            ctlAc.setLiquidCashAmount(ctlAc.getLiquidCashAmount().add(item.getVoucherAmount()));
                        } else if (ShroffConstant.CTL_AC_NATURE_LIA.equals(ctlAc.getBalanceNature())) {
                            ctlAc.setLiquidCashAmount(
                                CommonUtils.subtract(ctlAc.getLiquidCashAmount(), item.getVoucherAmount()));
                        }
                        credit = item.getVoucherAmount();
                    } else if (ShroffConstant.DR.equals(item.getDebitCredit())) {
                        if (ShroffConstant.CTL_AC_NATURE_AST.equals(ctlAc.getBalanceNature())) {
                            ctlAc.setLiquidCashAmount(ctlAc.getLiquidCashAmount().subtract(item.getVoucherAmount()));
                        } else if (ShroffConstant.CTL_AC_NATURE_LIA.equals(ctlAc.getBalanceNature())) {
                            ctlAc.setLiquidCashAmount(
                                CommonUtils.add(ctlAc.getLiquidCashAmount(), item.getVoucherAmount()));
                        }
                        debit = item.getVoucherAmount();
                    }

                    analysisCode = item.getAnalysisCode();
                    shrGeneralLedgerService.saveGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(), credit,
                        debit, ctlAc.getLiquidCashAmount(), null, ctlAcId, item.getNature(), voucher.getPoolRelated(),
                        voucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                    if (poolCaseAccId != null && CommonUtils.isNotBlank(analysisCode)) {
                        if (ShroffConstant.JOIN_POOL_VOUCHER_ITEM_ANALYSIS_CODE.equals(analysisCode) && debit != null
                            && debit.compareTo(BigDecimal.ZERO) > 0) {
                            shrGeneralLedgerService.saveGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(),
                                null, debit, BigDecimal.ZERO, poolCaseAccId, null, item.getNature(),
                                ShroffConstant.POOL_RELATED_JOIN_POOL_LEDGER, voucher.getCurrencyId(), null,
                                item.getChequeNo(), item.getRefName());
                        } else if (ShroffConstant.WITHDRAW_POOL_VOUCHER_ITEM_ANALYSIS_CODE.equals(analysisCode)
                            && credit != null && credit.compareTo(BigDecimal.ZERO) > 0) {
                            shrGeneralLedgerService.saveGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(),
                                credit, null, BigDecimal.ZERO, poolCaseAccId, null, item.getNature(),
                                ShroffConstant.POOL_RELATED_WITHDRAW_POOL_LEDGER, voucher.getCurrencyId(), null,
                                item.getChequeNo(), item.getRefName());
                        }
                    }
                }
            }
        }

        Map<String, Map> resultMap = subtractOnHoldAmountInPostJournalVoucher(accountMap, itemList, ctlAcMap);
        accountMap = resultMap.get("accountMap");
        ctlAcMap = resultMap.get("ctlAcMap");
        accountMap.forEach((k, v) -> v.setLastTransactionDate(businessDate));
        saveCaseAccountInfos(accountMap.values());
        saveControlAccountInfos(ctlAcMap.values());
        log.info("handleJournalVoucher() end");
    }

    private Map<String, Map> subtractOnHoldAmountInPostJournalVoucher(Map<Integer, CaseAccountInfoDTO> accountMap,
        List<VoucherItemDTO> itemList, Map<Integer, ControlAccountDTO> ctlAcMap) {
        Map<String, Map> resultMap = new HashMap<>();
        Map<Integer,
            BigDecimal> accountAmountCrMap = itemList.stream()
                .filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus())
                    && ShroffConstant.CR.equals(item.getDebitCredit()) && Objects.nonNull(item.getCaseAccount()))
                .collect(Collectors.groupingBy(e -> e.getCaseAccount().getCaseAcId())).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey,
                    e -> e.getValue().stream().filter(item -> Objects.nonNull(item.getVoucherAmount()))
                        .map(VoucherItemDTO::getVoucherAmount).reduce(BigDecimal.ZERO, BigDecimal::add)));
        Map<Integer,
            BigDecimal> accountAmountDrMap = itemList.stream()
                .filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus())
                    && ShroffConstant.DR.equals(item.getDebitCredit()) && Objects.nonNull(item.getCaseAccount()))
                .collect(Collectors.groupingBy(e -> e.getCaseAccount().getCaseAcId())).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey,
                    e -> e.getValue().stream().filter(item -> Objects.nonNull(item.getVoucherAmount()))
                        .map(VoucherItemDTO::getVoucherAmount).reduce(BigDecimal.ZERO, BigDecimal::add)));
        Map<Integer,
            BigDecimal> ctlAcAmountCRMap = itemList.stream()
                .filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus())
                    && ShroffConstant.CR.equals(item.getDebitCredit()) && Objects.nonNull(item.getControlAccount()))
                .collect(Collectors.groupingBy(e -> e.getControlAccount().getCtlAcId())).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey,
                    e -> e.getValue().stream().filter(item -> Objects.nonNull(item.getVoucherAmount()))
                        .map(VoucherItemDTO::getVoucherAmount).reduce(BigDecimal.ZERO, BigDecimal::add)));
        Map<Integer,
            BigDecimal> ctlAcAmountDRMap = itemList.stream()
                .filter(item -> !CoreConstant.STATUS_DELETE.equals(item.getStatus())
                    && ShroffConstant.DR.equals(item.getDebitCredit()) && Objects.nonNull(item.getControlAccount()))
                .collect(Collectors.groupingBy(e -> e.getControlAccount().getCtlAcId())).entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey,
                    e -> e.getValue().stream().filter(item -> Objects.nonNull(item.getVoucherAmount()))
                        .map(VoucherItemDTO::getVoucherAmount).reduce(BigDecimal.ZERO, BigDecimal::add)));
        accountMap.forEach(
            (k, v) -> v.setOnHoldAmountCr(CommonUtils.subtract(v.getOnHoldAmountCr(), accountAmountCrMap.get(k))));
        accountMap.forEach(
            (k, v) -> v.setOnHoldAmountDr(CommonUtils.subtract(v.getOnHoldAmountDr(), accountAmountDrMap.get(k))));
        ctlAcMap.forEach(
            (k, v) -> v.setOnHoldAmountCr(CommonUtils.subtract(v.getOnHoldAmountCr(), ctlAcAmountCRMap.get(k))));
        ctlAcMap.forEach(
            (k, v) -> v.setOnHoldAmountDr(CommonUtils.subtract(v.getOnHoldAmountDr(), ctlAcAmountDRMap.get(k))));
        resultMap.put("accountMap", accountMap);
        resultMap.put("ctlAcMap", ctlAcMap);
        return resultMap;
    }

    private void handlePaymentVoucher(VoucherDTO voucher, List<VoucherItemDTO> itemList) {
        log.info("handlePaymentVoucher() start - " + voucher);
        boolean breakdown = voucher.getBreakdownLedger() == null ? false : voucher.getBreakdownLedger();
        String defaultCode = null;
        List<AnalysisCodeDTO> asCodes
            = analysisCodeService.findByAnalysisCode(ShroffConstant.VOUCHER_DEFAULT_POSTING_ANALYSIS_CODE);
        Date businessDate = DateUtils.addDays(new Date(), Integer.valueOf(sysParamService.getBusinessOffset()));
        if (CommonUtils.isNotEmpty(asCodes)) {
            defaultCode = asCodes.get(0).getAnalysisCode();
        }
        defaultCode = CommonUtils.isBlank(voucher.getCtlAnalysisCode()) ? defaultCode : voucher.getCtlAnalysisCode();
        Map<Integer, CaseAccountInfoDTO> accountMap = new HashMap<>();
        Map<Integer, ControlAccountDTO> ctlAcMap = new HashMap<>();
        ControlAccountDTO controlAccount = voucher.getControlAccount();
        ctlAcMap.put(controlAccount.getCtlAcId(), controlAccount);
        ChequeDTO chequeDTO = null;
        if (ShroffConstant.VCR_PAYMENT_METHOD_CHEQUE.equals(voucher.getPaymentMethod())) {
            ShrCheque shrCheque = chequeRepository
                .findOne(ChequePredicate.findOutgoingChequeForPaymentVoucher(voucher.getVoucherId(), null));
            if (shrCheque != null) {
                chequeDTO = DataUtils.copyProperties(shrCheque, ChequeDTO.class);
                if (chequeDTO != null && chequeDTO.getParentChequeId() != null
                    && CoreConstant.CHEQUE_STATUS_COMBINED.equals(chequeDTO.getStatus())) {
                    chequeDTO = chequeService.getOutgoingChequeDetail(chequeDTO.getParentChequeId());
                }
                if (chequeDTO != null && CommonUtils.isBlank(chequeDTO.getChequeNo())) {
                    throw new BusinessException("payment voucher :" + voucher.getVoucherNo() + " no cheque no");
                }
                if (!CoreConstant.CHEQUE_STATUS_GENERATED.equals(chequeDTO.getStatus())) {
                    throw new BusinessException(
                        "The cheque of payment voucher : " + voucher.getVoucherNo() + " is not generated.");
                }
            }
        }
        BigDecimal voucherTotalAmount = voucher.getVoucherTotalAmount();

        Map<Integer, ControlAccountDTO> lastCtlAcMap = new HashMap<>();
        for (VoucherItemDTO item : itemList) {
            log.info("Voucher Item: " + item);
            BigDecimal amount = null;
            if (item.getVoucherAmount() != null) {
                if (ShroffConstant.DR.equals(item.getDebitCredit())) {
                    amount = item.getVoucherAmount().negate();
                } else {
                    amount = item.getVoucherAmount();
                }
            }
            CaseAccountInfoDTO account = null;
            ControlAccountDTO ctlAc = null;
            if (item.getCaseAccount() != null && item.getCaseAccount().getCaseAcId() != null
                && item.getCaseAccount().getCaseAcId() != 0) {
                Integer caseAcId = item.getCaseAccount().getCaseAcId();
                if (accountMap.containsKey(caseAcId)) {
                    account = accountMap.get(caseAcId);
                } else {
                    account = item.getCaseAccount();
                    accountMap.put(caseAcId, account);
                }
            } else if (item.getControlAccount() != null && item.getControlAccount().getCtlAcId() != null
                && item.getControlAccount().getCtlAcId() != 0) {
                Integer ctlAcId = item.getControlAccount().getCtlAcId();
                if (accountMap.containsKey(ctlAcId)) {
                    ctlAc = ctlAcMap.get(ctlAcId);
                } else {
                    if (!lastCtlAcMap.isEmpty() && lastCtlAcMap.containsKey(ctlAcId)) {
                        ctlAc = lastCtlAcMap.get(ctlAcId);
                    } else {
                        ctlAc = item.getControlAccount();
                    }
                    ctlAcMap.put(ctlAcId, ctlAc);
                }
            }

            changeAmountForAccount(amount, account, ctlAc, voucher, item, chequeDTO, null, defaultCode, false);
            if (breakdown) {
                changeAmountForAccount(amount.negate(), null, controlAccount, voucher, item, chequeDTO, null,
                    defaultCode, false);
            }
            if (ctlAc != null) {
                lastCtlAcMap.put(ctlAc.getCtlAcId(), ctlAc);
            }
        }
        if (!breakdown) {
            changeAmountForAccount(voucherTotalAmount, null, controlAccount, voucher, null, chequeDTO, null,
                defaultCode, false);
        }
        accountMap.forEach((k, v) -> v.setLastTransactionDate(businessDate));
        saveCaseAccountInfos(accountMap.values());
        saveControlAccountInfos(ctlAcMap.values());
        log.info("handlePaymentVoucher() end");
    }

    // positive amount add
    // negative amount sub
    private void changeAmountForAccount(BigDecimal amount, CaseAccountInfoDTO caseAccount,
        ControlAccountDTO controlAccount, VoucherDTO voucher, VoucherItemDTO item, ChequeDTO cheque,
        String chequeNumber, String mainAnalysisCode, Boolean chargeRealizarionFee) {
        log.info("changeAmountForAccount() start");
        if (amount == null || (caseAccount == null && controlAccount == null)) {
            throw new BusinessException("no necessary data.");
        }
        String particular = item != null ? item.getNature() : voucher.getCtlParticular();
        particular = chargeRealizarionFee ? (caseAccount != null ? ShroffConstant.REALIZATION_FEE_CASE_ACC_PARTICULAR
            : ShroffConstant.REALIZATION_FEE_CON_ACC_PARTICULAR) : particular;
        String analysisCode = item != null && !chargeRealizarionFee ? item.getAnalysisCode() : mainAnalysisCode;
        String refName = chargeRealizarionFee ? null : collectRefName(voucher, item);
        if (amount.compareTo(BigDecimal.ZERO) < 0) {
            amount = amount.negate();
            if (caseAccount != null) {
                caseAccount.setOnHoldAmountDr(CommonUtils.subtract(caseAccount.getOnHoldAmountDr(), amount));
                caseAccount.setLiquidCashAmount(CommonUtils.subtract(caseAccount.getLiquidCashAmount(), amount));
                if (voucher.getIsAutoPaymentVoucher() != null && voucher.getIsAutoPaymentVoucher()) {
                    shrGeneralLedgerService.saveAutoGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(), null,
                        amount, caseAccount.getLiquidCashAmount(), caseAccount.getCaseAcId(), null, particular,
                        voucher.getPoolRelated(), voucher.getCurrencyId(), cheque, chequeNumber, refName);
                } else {
                    shrGeneralLedgerService.saveGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(), null,
                        amount, caseAccount.getLiquidCashAmount(), caseAccount.getCaseAcId(), null, particular,
                        voucher.getPoolRelated(), voucher.getCurrencyId(), cheque, chequeNumber, refName);
                }
            } else {
                controlAccount.setOnHoldAmountDr(CommonUtils.subtract(controlAccount.getOnHoldAmountDr(), amount));
                if (ShroffConstant.CTL_AC_NATURE_AST.equals(controlAccount.getBalanceNature())) {
                    controlAccount
                        .setLiquidCashAmount(CommonUtils.subtract(controlAccount.getLiquidCashAmount(), amount));
                    controlAccount.setBalance(CommonUtils.add(controlAccount.getBalance(), amount));
                } else if (ShroffConstant.CTL_AC_NATURE_LIA.equals(controlAccount.getBalanceNature())) {
                    controlAccount.setLiquidCashAmount(CommonUtils.add(controlAccount.getLiquidCashAmount(), amount));
                    controlAccount.setBalance(CommonUtils.subtract(controlAccount.getBalance(), amount));
                }
                if (voucher.getVoucherType().getVoucherTypeCode().equals(ShroffConstant.VT_PAY)
                    && ShroffConstant.PAY_METHOD_TRANSFER_TO_CONTROL.equals(voucher.getPaymentMethod())) {
                    refName = null;
                }
                if (voucher.getIsAutoPaymentVoucher() != null && voucher.getIsAutoPaymentVoucher()) {
                    shrGeneralLedgerService.saveAutoGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(), null,
                        amount, controlAccount.getLiquidCashAmount(), null, controlAccount.getCtlAcId(), particular,
                        voucher.getPoolRelated(), voucher.getCurrencyId(), cheque, chequeNumber, refName);
                } else {
                    shrGeneralLedgerService.saveGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(), null,
                        amount, controlAccount.getLiquidCashAmount(), null, controlAccount.getCtlAcId(), particular,
                        voucher.getPoolRelated(), voucher.getCurrencyId(), cheque, chequeNumber, refName);
                }
            }
        } else if (amount.compareTo(BigDecimal.ZERO) > 0) {
            if (caseAccount != null) {
                caseAccount.setOnHoldAmountCr(CommonUtils.subtract(caseAccount.getOnHoldAmountCr(), amount));
                caseAccount.setLiquidCashAmount(CommonUtils.add(caseAccount.getLiquidCashAmount(), amount));
                if (voucher.getIsAutoPaymentVoucher() != null && voucher.getIsAutoPaymentVoucher()) {
                    shrGeneralLedgerService.saveAutoGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(),
                        amount, null, caseAccount.getLiquidCashAmount(), caseAccount.getCaseAcId(), null, particular,
                        voucher.getPoolRelated(), voucher.getCurrencyId(), cheque, chequeNumber, refName);
                } else {
                    shrGeneralLedgerService.saveGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(), amount,
                        null, caseAccount.getLiquidCashAmount(), caseAccount.getCaseAcId(), null, particular,
                        voucher.getPoolRelated(), voucher.getCurrencyId(), cheque, chequeNumber, refName);
                }
            } else {
                controlAccount.setOnHoldAmountCr(CommonUtils.subtract(controlAccount.getOnHoldAmountCr(), amount));
                if (ShroffConstant.CTL_AC_NATURE_AST.equals(controlAccount.getBalanceNature())) {
                    controlAccount.setLiquidCashAmount(CommonUtils.add(controlAccount.getLiquidCashAmount(), amount));
                    controlAccount.setBalance(CommonUtils.subtract(controlAccount.getBalance(), amount));
                } else if (ShroffConstant.CTL_AC_NATURE_LIA.equals(controlAccount.getBalanceNature())) {
                    controlAccount
                        .setLiquidCashAmount(CommonUtils.subtract(controlAccount.getLiquidCashAmount(), amount));
                    controlAccount.setBalance(CommonUtils.add(controlAccount.getBalance(), amount));
                }
                if (voucher.getVoucherType().getVoucherTypeCode().equals(ShroffConstant.VT_PAY)
                    && ShroffConstant.PAY_METHOD_TRANSFER_TO_CONTROL.equals(voucher.getPaymentMethod())) {
                    refName = null;
                }
                if (voucher.getIsAutoPaymentVoucher() != null && voucher.getIsAutoPaymentVoucher()) {
                    shrGeneralLedgerService.saveAutoGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(),
                        amount, null, controlAccount.getLiquidCashAmount(), null, controlAccount.getCtlAcId(),
                        particular, voucher.getPoolRelated(), voucher.getCurrencyId(), cheque, chequeNumber, refName);
                } else {
                    shrGeneralLedgerService.saveGeneralLedgerForBatchJob(analysisCode, voucher.getVoucherId(), amount,
                        null, controlAccount.getLiquidCashAmount(), null, controlAccount.getCtlAcId(), particular,
                        voucher.getPoolRelated(), voucher.getCurrencyId(), cheque, chequeNumber, refName);
                }
            }
        }
        log.info("changeAmountForAccount() end");
    }

    private String collectRefName(VoucherDTO voucher, VoucherItemDTO item) {
        String result = null;
        if (item != null) {
            if (CommonUtils.isNotBlank(item.getRefName())) {
                result = item.getRefName();
            } else if (CommonUtils.isNotBlank(item.getReceiver())) {
                result = item.getReceiver();
            }
        }
        if (result == null && voucher != null) {
            if (CommonUtils.isNotBlank(voucher.getPayeeName())) {
                result = voucher.getPayeeName();
            } else if (voucher.getComPayee() != null) {
                result = voucher.getComPayee().getPayeeName();
            }
        }
        return result;
    }

    private void handleReceiptVoucher(VoucherDTO voucher, List<VoucherItemDTO> itemList) {
        log.info("handleReceiptVoucher() start - " + voucher);
        boolean breakdown = voucher.getBreakdownLedger() == null ? false : voucher.getBreakdownLedger();
        String defaultCode = null;
        List<AnalysisCodeDTO> asCodes
            = analysisCodeService.findByAnalysisCode(ShroffConstant.VOUCHER_DEFAULT_POSTING_ANALYSIS_CODE);
        Date businessDate = DateUtils.addDays(new Date(), Integer.valueOf(sysParamService.getBusinessOffset()));
        if (CommonUtils.isNotEmpty(asCodes)) {
            defaultCode = asCodes.get(0).getAnalysisCode();
        }
        defaultCode = CommonUtils.isBlank(voucher.getCtlAnalysisCode()) ? defaultCode : voucher.getCtlAnalysisCode();
        Map<Integer, CaseAccountInfoDTO> accountMap = new HashMap<>();
        Map<Integer, ControlAccountDTO> ctlAcMap = new HashMap<>();
        ControlAccountDTO recCtlAc = voucher.getControlAccount();
        ctlAcMap.put(recCtlAc.getCtlAcId(), recCtlAc);

        for (VoucherItemDTO item : itemList) {

            BigDecimal amount = null;
            if (item.getVoucherAmount() != null) {
                if (ShroffConstant.DR.equals(item.getDebitCredit())) {
                    amount = item.getVoucherAmount().negate();
                } else {
                    amount = item.getVoucherAmount();
                }
            }
            CaseAccountInfoDTO account = null;
            ControlAccountDTO ctlAc = null;
            if (item.getCaseAccount() != null && item.getCaseAccount().getCaseAcId() != null
                && item.getCaseAccount().getCaseAcId() != 0) {
                Integer caseAcId = item.getCaseAccount().getCaseAcId();
                if (accountMap.containsKey(caseAcId)) {
                    account = accountMap.get(caseAcId);
                } else {
                    account = item.getCaseAccount();
                    accountMap.put(caseAcId, account);
                }
            } else if (item.getControlAccount() != null && item.getControlAccount().getCtlAcId() != null
                && item.getControlAccount().getCtlAcId() != 0) {
                Integer ctlAcId = item.getControlAccount().getCtlAcId();
                if (ctlAcMap.containsKey(ctlAcId)) {
                    ctlAc = ctlAcMap.get(ctlAcId);
                } else {
                    ctlAc = item.getControlAccount();
                    ctlAcMap.put(ctlAcId, ctlAc);
                }
            }
            // Realization Fee
            if (account != null && voucher.getRealizationFee() != null && voucher.getRealizationFee()) {
                Map<String, Object> map
                    = handleRealizationFee(voucher.getVoucherDate(), ctlAcMap, account, item.getAnalysisCode());
                Boolean chargeRealizationFee = (Boolean)map.get("chargeRealizationFee");
                if (chargeRealizationFee) {
                    ControlAccountDTO controlAccountDTO = (ControlAccountDTO)map.get("controlAccountDTO");
                    BigDecimal realizationFee = (BigDecimal)map.get("realizationFee");
                    String analysisCode = ShroffConstant.REALIZATION_FEE_BCASE_ACODE;
                    if (account.getCaseAccountCode1().equals(ShroffConstant.VOUCHER_GROUP_CODE_LIQUIDATION)) {
                        analysisCode = ShroffConstant.REALIZATION_FEE_LCASE_ACODE;
                    }
                    changeAmountForAccount(realizationFee.negate(), account, null, voucher, item, null, null,
                        analysisCode, true);
                    changeAmountForAccount(realizationFee, null, controlAccountDTO, voucher, item, null, null,
                        analysisCode, true);
                } else {
                    Voucher voucher1 = voucherRepository.findVoucherByVoucherId(voucher.getVoucherId());
                    voucher1.setRealizationFee(false);
                    voucherRepository.save(voucher1);
                }

            }

            changeAmountForAccount(amount, account, ctlAc, voucher, item, item.getShrCheque(), null, defaultCode,
                false);
            if (breakdown) {
                changeAmountForAccount(amount.negate(), null, recCtlAc, voucher, item, null, null, defaultCode, false);
            }
        }
        if (!breakdown) {
            changeAmountForAccount(voucher.getVoucherTotalAmount().negate(), null, recCtlAc, voucher, null, null, null,
                defaultCode, false);
        }
        accountMap.forEach((k, v) -> v.setLastTransactionDate(businessDate));
        saveCaseAccountInfos(accountMap.values());
        saveControlAccountInfos(ctlAcMap.values());

        log.info("handleReceiptVoucher() end");
    }

    private Integer totalLTminFee(CaseAccountInfoDTO account, Date voucherDate) {
        BigDecimal alreadyChargedAmount
            = shrGeneralLedgerService.getORFeeItemChargedAmount(account.getCaseAcId(), "255-011", "255-029", true);
        Integer caseFeeTypeId = 10;
        CaseFeeMain caseFeeMain = caseFeeMainRepository
            .findOne(CaseFeeMainPredicate.findByCaseFeeTypeIdAndDate(caseFeeTypeId, voucherDate));
        BigDecimal minimumFee = null;
        if (null != caseFeeMain) {
            minimumFee = caseFeeMain.getFeeAmount();
        }
        int i = minimumFee.subtract(alreadyChargedAmount).compareTo(BigDecimal.ZERO);
        // i=1 收取 ， i=0 相等(不收取) ， i=-1 不收取
        return i;
    }

    private Map<String, Object> handleRealizationFee(Date voucherDate, Map<Integer, ControlAccountDTO> ctlAcMap,
        CaseAccountInfoDTO account, String analysisCode) {
        log.info("handleRealizationFee() start - voucherDate : " + voucherDate + ", ctlAcMap : " + ctlAcMap
            + ", account : " + account + ", analysisCode : " + analysisCode);
        Map<String, Object> resultMap = new HashMap<>();
        Boolean chargeRealizationFee = false;
        AnalysisCodeDTO analysisCodeDTO = analysisCodeService.findByAnalysisCode(analysisCode).get(0);
        ControlAccountDTO controlAccountDTO = null;
        if (CommonUtils.isNotBlank(analysisCodeDTO.getRealizationFee())
            && "YES".equals(analysisCodeDTO.getRealizationFee())) {
            chargeRealizationFee = true;
            Integer caseFeeTypeId = null;
            String realizationFeeCa = null;
            if (account != null && account.getCaseAccountCode1().equals(ShroffConstant.VOUCHER_GROUP_CODE_BANKRUPTCY)) {
                realizationFeeCa = ShroffConstant.REALIZATION_FEE_BCASE_CA;
                caseFeeTypeId = 2;
            } else if (account != null
                && account.getCaseAccountCode1().equals(ShroffConstant.VOUCHER_GROUP_CODE_LIQUIDATION)) {
                realizationFeeCa = ShroffConstant.REALIZATION_FEE_LCASE_CA;
                caseFeeTypeId = 16;
            }
            if (null != realizationFeeCa) {
                controlAccountDTO = controlAccountService.findByAccountNumber(realizationFeeCa);
                if (ctlAcMap.get(controlAccountDTO.getCtlAcId()) != null) {
                    controlAccountDTO = ctlAcMap.get(controlAccountDTO.getCtlAcId());
                }
                ctlAcMap.put(controlAccountDTO.getCtlAcId(), controlAccountDTO);
                resultMap.put("controlAccountDTO", controlAccountDTO);
            }
            CaseFeeMain caseFeeMain = null;
            if (null != caseFeeTypeId) {
                caseFeeMain = caseFeeMainRepository
                    .findOne(CaseFeeMainPredicate.findByCaseFeeTypeIdAndDate(caseFeeTypeId, voucherDate));
            }
            BigDecimal realizationFee = null;
            if (null != caseFeeMain) {
                realizationFee = caseFeeMain.getFeeAmount();
                resultMap.put("realizationFee", realizationFee);
            }
            chargeRealizationFee = totalLTminFee(account, voucherDate) == 1 ? true : false;
        }
        resultMap.put("chargeRealizationFee", chargeRealizationFee);
        log.info("handleRealizationFee() end - result = " + resultMap);
        return resultMap;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer createPaymentVoucherForOutsiderTrustee(CaseDTO caseDTO) {
        log.info("createPaymentVoucherForOutsiderTrustee() start - caseDTO = " + caseDTO);
        Integer voucherId = null;
        if (caseDTO != null) {
            Voucher paymentVoucher = new Voucher();
            Case caseInfo = DataUtils.copyProperties(caseDTO, Case.class);

            VoucherType voucherType = voucherTypeRepository.findByVoucherTypeCode(ShroffConstant.VT_PAY);
            paymentVoucher.setVoucherType(voucherType);

            CaseFee caseFee = caseInfo.getCaseFee();
            if (caseFee != null) {
                paymentVoucher.setVoucherTotalAmount(caseFee.getFeeAmount());
            } else {
                return null;
            }

            paymentVoucher.setPreparerId(SecurityUtils.getCurrentPostId());
            paymentVoucher.setIsHardCopy(false);
            paymentVoucher.setPoolRelated(ShroffConstant.POOL_RELATED_D);
            paymentVoucher.setStatus(CoreConstant.VOUCHER_STATUS_DRAFT);
            paymentVoucher.setPayeeName(ShroffConstant.THE_GOVERNMENT_OF_HKSAR);

            voucherRepository.save(paymentVoucher);
            voucherId = paymentVoucher.getVoucherId();

            log.info("createPaymentVoucherForOutsiderTrustee() end - voucherId = " + voucherId);
        }
        return voucherId;
    }

    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public Integer remitBalancePaymentVoucher(CaseDTO caseDTO) {
        log.info("remitBalancePaymentVoucher() start - caseDTO = " + caseDTO);
        Integer voucherId = null;
        if (caseDTO != null) {
            Voucher paymentVoucher = new Voucher();
            Case caseInfo = DataUtils.copyProperties(caseDTO, Case.class);

            VoucherType voucherType = voucherTypeRepository.findByVoucherTypeCode(ShroffConstant.VT_PAY);
            paymentVoucher.setVoucherType(voucherType);

            CaseFee caseFee = caseInfo.getCaseFee();
            BigDecimal feeAmount = BigDecimal.ZERO;
            if (caseFee != null) {
                feeAmount = caseFee.getFeeAmount();
            } else {
                return null;
            }
            List<CaseAccountInfo> accountInfos = (List<CaseAccountInfo>)caseAccountInfoRepository
                .findAll(CaseAccountInfoPredicate.findAllAccountByCaseId(caseInfo.getCaseId()));
            BigDecimal allAccountAmount = BigDecimal.ZERO;
            if (CommonUtils.isNotEmpty(accountInfos)) {
                for (CaseAccountInfo info : accountInfos) {
                    allAccountAmount = allAccountAmount.add(info.getLiquidCashAmount()).add(info.getInvestmentAmount());
                }
            }
            paymentVoucher.setVoucherTotalAmount(allAccountAmount.subtract(feeAmount));
            paymentVoucher.setPreparerId(SecurityUtils.getCurrentPostId());
            paymentVoucher.setIsHardCopy(false);
            paymentVoucher.setPoolRelated(ShroffConstant.POOL_RELATED_D);
            paymentVoucher.setStatus(CoreConstant.VOUCHER_STATUS_DRAFT);
            paymentVoucher.setPayeeName(ShroffConstant.BANKRUPTCY_ESTATE_OF_CASE_NAME);

            voucherRepository.save(paymentVoucher);

            log.info("remitBalancePaymentVoucher() end - voucherId = " + voucherId);
        }
        return voucherId;
    }

    @Override
    @Transactional(readOnly = true)
    public Voucher createAutoVoucher(String voucherType, BigDecimal totalAmount, String poolRelated, String groupCode) {
        Date current = DateUtils.getCurrentDate();
        Voucher voucher = new Voucher();
        voucher.setVoucherType(voucherTypeRepository.findByVoucherTypeCode(voucherType));
        String voucherNo = null;
        if (ShroffConstant.VT_JOU.equals(voucherType)) {
            voucherNo = getAutoVoucherNo(SysSequenceEnum.JOU_VOUCHER_SEQ_NO.name());
            voucher
                .setJournalType(journalTypeRepository.findByJournalTypeCode(FundsConstant.AUTO_VOUCHER_JOURNAL_TYPE));
        } else if (ShroffConstant.VT_REC.equals(voucherType)) {
            voucherNo = getAutoVoucherNo(SysSequenceEnum.REC_VOUCHER_SEQ_NO.name());
            voucher.setReceiveMethod(ShroffConstant.VCR_RECEIVE_METHOD_BANK_TRANSFER);
        } else if (ShroffConstant.VT_PAY.equals(voucherType)) {
            voucherNo = getAutoVoucherNo(SysSequenceEnum.PAY_VOUCHER_SEQ_NO.name());
            voucher.setPaymentMethod(ShroffConstant.VCR_PAYMENT_METHOD_CHEQUE);
        }
        voucher.setGroupCode(groupCode);
        voucher.setVoucherNo(voucherNo);
        Integer autoVoucherPost = getAutoVoucherPost();
        voucher.setPreparerId(autoVoucherPost);
        voucher.setFirstApproverId(autoVoucherPost);
        voucher.setSecondApproverId(autoVoucherPost);
        voucher.setPaymentVerifierId(autoVoucherPost);
        voucher.setVoucherTotalAmount(totalAmount);
        voucher.setIsHardCopy(false);
        voucher.setStatus(CoreConstant.VOUCHER_STATUS_VERIFIED);
        voucher.setVoucherDate(current);
        voucher.setSubmissionDate(current);
        voucher.setCurrencyId(ShroffConstant.CURCY_HKD);
        voucher.setSourceCode(ShroffConstant.VOUCHER_SOURCE_CODE_00);
        voucher.setPoolRelated(poolRelated);
        return voucher;
    }

    private String getAutoVoucherNo(String voucherType) {
        StringBuilder sb = new StringBuilder();
        String voucherNo = sysSequenceService.generateVoucherSeqNo(voucherType);
        if (voucherNo.length() > 2) {
            voucherNo = voucherNo.substring(2, voucherNo.length());
        }
        sb.append(voucherNo).append(FundsConstant.AUTO_VOUCHER_NO_SUFFIX);
        return sb.toString();
    }

    private Integer getAutoVoucherPost() {
        ApplicationCodeTableDTO applicationCodeTableDTO = commonService.searchAutoVoucherPost();
        if (applicationCodeTableDTO != null) {
            return Integer.valueOf(applicationCodeTableDTO.getCodeValue());
        }
        return null;
    }

    @Override
    public ShrVcrItem createAutoVoucherItem(BigDecimal amount, CaseAccountInfo caseAccountInfo, ShrCtlAc shrCtlAc,
        String debitCredit, Integer analysisCodeId, String analysisCode, Integer itemNo) {
        ShrVcrItem vcrItem = new ShrVcrItem();
        vcrItem.setVoucherAmount(amount);
        vcrItem.setDebitCredit(debitCredit);
        vcrItem.setControlAccount(shrCtlAc);
        vcrItem.setCaseAccount(caseAccountInfo);
        vcrItem.setAnalysisCodeId(analysisCodeId);
        vcrItem.setAnalysisCode(analysisCode);
        if (analysisCodeId != null) {
            AnalysisCode ac = analysisCodeRepository.findOne(analysisCodeId);
            if (ac != null) {
                vcrItem.setNature(ac.getFullName());
                vcrItem.setAnalysisCode(ac.getAnalysisCode());
            }
        }
        vcrItem.setStatus(CoreConstant.STATUS_ACTIVE);
        vcrItem.setVoucherItemNo(itemNo);
        return vcrItem;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void approvePendingPaymentVoucher() {
        log.info("approvePendingPaymentVoucher start");

        Long txnKey = sysSequenceService.generateTxnKeyRefNo();
        String defaultUserName = null;
        String defaultUserPost = null;
        defaultUserName = sysParamService.getDefaultUserName();
        defaultUserPost = sysParamService.getDefaultUserPost();

        Date currentDate = commonService.addWorkingDays(new Date(), 2);

        UserInfo userInfo = new UserInfo();
        userInfo.setUserName(defaultUserName);
        userInfo.setUserPost(defaultUserPost);
        RequestContextUtils.setCurrentUserInfo(userInfo);
        RequestContextUtils.setTxnKeyRef(txnKey);

        List<ShrPendingMoney> shrPendingMonies
            = pendingMoneyRepository.findAllByCaseAccountReverseMoneyFundsAvailableDateLessThanEqualAndStatus(
                DateUtils.getEndDate(currentDate), CoreConstant.STATUS_ACTIVE);
        for (ShrPendingMoney pendingMoney : shrPendingMonies) {
            CaseAccountInfo caseAccountInfo = null;
            BigDecimal amount = null;
            if (ShroffConstant.PENDING_MONEY_TYPE_UPLIFT.equals(pendingMoney.getPendingMoneyType())) {
                CaseAccountReverseMoney reverseMoney = pendingMoney.getCaseAccountReverseMoney();
                caseAccountInfo = reverseMoney.getCaseAccountInfo();
                amount = reverseMoney.getUpliftAmount();
            } else if (ShroffConstant.PENDING_MONEY_TYPE_BORROW_MONEY.equals(pendingMoney.getPendingMoneyType())) {
                Voucher voucher = voucherRepository.findOne(pendingMoney.getJournalVoucherId());
                List<ShrVcrItem> items = voucher.getShrVcrItems();
                for (ShrVcrItem item : items) {
                    if (ShroffConstant.CR.equals(item.getDebitCredit())) {
                        caseAccountInfo = item.getCaseAccount();
                        amount = item.getVoucherAmount();
                        break;
                    }
                }
            }
            if (caseAccountInfo == null || amount == null) {
                continue;
            }
            if (caseAccountInfo.getInvestmentAmount().compareTo(amount) >= 0) {
                Voucher paymentVoucher = voucherRepository.findVoucherByVoucherIdAndStatus(
                    pendingMoney.getPaymentVoucherId(), CoreConstant.VOUCHER_STATUS_WAITING_PENDING_MONEY);
                if (paymentVoucher != null) {
                    paymentVoucher.setStatus(CoreConstant.VOUCHER_STATUS_APPROVED);
                    voucherRepository.save(paymentVoucher);
                }
            }
        }
        log.info("approvePendingPaymentVoucher end");
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer pendingForPaymentReceiptVoucher(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        log.info("pendingForPaymentReceiptVoucher() start - receiptVoucherDetail: " + receiptVoucherDetail);
        prepareReceiptVoucherWorkFlow(receiptVoucherDetail.getReceiptVoucherBasicInformation(),
            WorkFlowAction.PendingForPayment.getCode());
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo = receiptVoucherDetail.getReceiptVoucherBasicInformation();
        Long workflowId = saveWorkFlowHistory(receiptVoucherBasicInfo.getWorkflowId(),
            receiptVoucherBasicInfo.getSysWorkFlowRule(), null);

        if (CommonUtils.isBlank(receiptVoucherBasicInfo.getVoucherNo())) {
            String voucherNumber = genVoucherNumber(receiptVoucherBasicInfo.getGroupCode(), ShroffConstant.VT_REC,
                SysSequenceEnum.REC_VOUCHER_SEQ_NO.name());
            receiptVoucherBasicInfo.setVoucherNo(voucherNumber);
        }

        receiptVoucherBasicInfo.setWorkflowId(workflowId);

        Integer voucherId = saveReceiptVoucherInside(receiptVoucherDetail);
        log.info("pendingForPaymentReceiptVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer paymentReceivedReceiptVoucher(ReceiptVoucherDetailDTO receiptVoucherDetail) {
        log.info("paymentReceivedReceiptVoucher() start - receiptVoucherDetail: " + receiptVoucherDetail);
        prepareReceiptVoucherWorkFlow(receiptVoucherDetail.getReceiptVoucherBasicInformation(),
            WorkFlowAction.PaymentReceived.getCode());
        ReceiptVoucherBasicInfoDTO receiptVoucherBasicInfo = receiptVoucherDetail.getReceiptVoucherBasicInformation();
        Long workflowId = saveWorkFlowHistory(receiptVoucherBasicInfo.getWorkflowId(),
            receiptVoucherBasicInfo.getSysWorkFlowRule(), null);

        if (CommonUtils.isBlank(receiptVoucherBasicInfo.getVoucherNo())) {
            String voucherNumber = genVoucherNumber(receiptVoucherBasicInfo.getGroupCode(), ShroffConstant.VT_REC,
                SysSequenceEnum.REC_VOUCHER_SEQ_NO.name());
            receiptVoucherBasicInfo.setVoucherNo(voucherNumber);
        }

        receiptVoucherBasicInfo.setWorkflowId(workflowId);

        Integer voucherId = saveReceiptVoucherInside(receiptVoucherDetail);

        // Create a new receipt
        ReceiptDTO receiptDTO = new ReceiptDTO();
        receiptDTO.setReceiptNo(receiptService.generateReceiptNumber());
        receiptDTO.setPaymentType(ShroffConstant.RECEIPT_PAYMENT_TYPE_CASH);
        receiptDTO.setPayerName(" ");
        receiptDTO.setReceiptAmount(receiptVoucherBasicInfo.getVoucherTotalAmount());
        receiptDTO.setReceiveDate(DateUtils.addDays(new Date(), Integer.valueOf(sysParamService.getBusinessOffset())));
        receiptDTO.setStatus(CoreConstant.STATUS_ACTIVE);
        VoucherDTO voucherDTO = new VoucherDTO();
        voucherDTO.setVoucherId(voucherId);
        receiptDTO.setShrVcrInfo(voucherDTO);
        receiptService.saveReceipt(receiptDTO);

        log.info("paymentReceivedReceiptVoucher() end - VoucherId: " + voucherId);
        return voucherId;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public Integer savePayerNameAndContactNameWhenPrintReceipt(ReceiptDTO receiptDTO) {
        log.info("savePayerNameAndContactNameWhenPrintReceipt Service start.");
        Integer result = null;
        ShrReceipt shrReceipt = receiptService.saveReceipt(receiptDTO);
        if (shrReceipt != null) {
            result = shrReceipt.getReceiptId();
        }
        log.info("savePayerNameAndContactNameWhenPrintReceipt Service end.");
        return result;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public String createAutoVoucherToReverseOriginVoucher(Integer originVoucherId, Boolean genLedgerNow) {
        log.info("createAutoVoucherToReverseOriginVoucher() start - originVoucherId :" + originVoucherId);
        Voucher originVoucher = voucherRepository.findVoucherByVoucherId(originVoucherId);
        Voucher autoJournalVoucher = null;
        if (originVoucher != null) {
            if (!genLedgerNow) {
                originVoucher.setStatus(CoreConstant.VOUCHER_STATUS_REVERSED);
                voucherRepository.save(originVoucher);
            }
            autoJournalVoucher = createAutoVoucher(ShroffConstant.VT_JOU, originVoucher.getVoucherTotalAmount(),
                originVoucher.getPoolRelated(), originVoucher.getGroupCode());
            autoJournalVoucher.setVoucherDate(DateUtils.getCurrentDate());
            if (genLedgerNow) {
                autoJournalVoucher.setStatus(CoreConstant.VOUCHER_STATUS_POSTED);
            }
            autoJournalVoucher = voucherRepository.save(autoJournalVoucher);
            switch (originVoucher.getVoucherType().getVoucherTypeCode()) {
                case ShroffConstant.VT_JOU:
                    createVoucherToReverseJournalVoucher(originVoucher, autoJournalVoucher, genLedgerNow);
                    break;
                case ShroffConstant.VT_PAY:
                case ShroffConstant.VT_REC:
                    createVoucherToReversePaymentAndReceiptVoucher(originVoucher, autoJournalVoucher, genLedgerNow);
                    break;
                default:
                    break;
            }
        }
        String voucherNo = null;
        if(autoJournalVoucher != null){
            voucherNo = autoJournalVoucher.getVoucherNo();
        }
        log.info("createAutoVoucherToReverseOriginVoucher() end ");
        return voucherNo;
    }

    private void createVoucherToReversePaymentAndReceiptVoucher(Voucher originVoucher, Voucher autoJournalVoucher,
        Boolean genLedgerNow) {
        log.info("createVoucherToReversePaymentAndReceiptVoucher() start - originVoucher : " + originVoucher
            + " autoJournalVoucher : " + autoJournalVoucher);
        List<ShrVcrItem> shrVcrItems = originVoucher.getShrVcrItems();
        ShrCtlAc controlAccount = originVoucher.getControlAccount();
        ShrVcrItem debitItem = null;
        ShrVcrItem creditItem = null;
        Map<Integer, CaseAccountInfo> caseAccountMap = new HashMap<>();
        if (CommonUtils.isNotEmpty(shrVcrItems)) {
            for (ShrVcrItem item : shrVcrItems) {
                if (!ObjectUtils.isEmpty(item.getCaseAccount())) {
                    CaseAccountInfo caseAccountInfo
                        = caseAccountMap.getOrDefault(item.getCaseAccount().getCaseAcId(), item.getCaseAccount());
                    caseAccountMap.putIfAbsent(item.getCaseAccount().getCaseAcId(), item.getCaseAccount());
                    switch (item.getDebitCredit()) {
                        case ShroffConstant.DR:
                            creditItem = createAutoVoucherItem(item.getVoucherAmount(), caseAccountInfo, null,
                                ShroffConstant.CR, item.getAnalysisCodeId(), item.getAnalysisCode(),
                                item.getVoucherItemNo() * 2 - 1);
                            creditItem.setVoucherId(autoJournalVoucher.getVoucherId());
                            creditItem.setRefName(caseAccountInfo.getCaseAccountName());
                            creditItem.setNature(item.getNature());
                            shrVcrItemRepository.save(creditItem);

                            debitItem = createAutoVoucherItem(item.getVoucherAmount(), null, controlAccount,
                                ShroffConstant.DR, item.getAnalysisCodeId(), item.getAnalysisCode(),
                                item.getVoucherItemNo() * 2);
                            debitItem.setVoucherId(autoJournalVoucher.getVoucherId());
                            debitItem.setRefName(controlAccount.getCtlAcName());
                            debitItem.setNature(item.getNature());
                            shrVcrItemRepository.save(debitItem);
                            if (genLedgerNow) {
                                caseAccountInfo.setLiquidCashAmount(
                                    CommonUtils.add(caseAccountInfo.getLiquidCashAmount(), item.getVoucherAmount()));
                                BigDecimal liqCash
                                    = ShroffConstant.CTL_AC_NATURE_LIA.equals(controlAccount.getBalanceNature())
                                        ? CommonUtils.add(controlAccount.getLiquidCashAmount(), item.getVoucherAmount())
                                        : CommonUtils.subtract(controlAccount.getLiquidCashAmount(),
                                            item.getVoucherAmount());
                                controlAccount.setLiquidCashAmount(liqCash);
                                shrGeneralLedgerService.saveGeneralLedgerForBatchJob(item.getAnalysisCode(),
                                    autoJournalVoucher.getVoucherId(), item.getVoucherAmount(), null,
                                    caseAccountInfo.getLiquidCashAmount(), caseAccountInfo.getCaseAcId(), null,
                                    item.getNature(), autoJournalVoucher.getPoolRelated(),
                                    autoJournalVoucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                                shrGeneralLedgerService.saveGeneralLedgerForBatchJob(item.getAnalysisCode(),
                                    autoJournalVoucher.getVoucherId(), null, item.getVoucherAmount(),
                                    controlAccount.getLiquidCashAmount(), null, controlAccount.getCtlAcId(),
                                    item.getNature(), autoJournalVoucher.getPoolRelated(),
                                    autoJournalVoucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                            } else {
                                caseAccountInfo.setOnHoldAmountCr(
                                    CommonUtils.add(caseAccountInfo.getOnHoldAmountCr(), item.getVoucherAmount()));
                                controlAccount.setOnHoldAmountDr(
                                    CommonUtils.add(controlAccount.getOnHoldAmountDr(), item.getVoucherAmount()));
                            }
                            break;
                        case ShroffConstant.CR:
                            creditItem = createAutoVoucherItem(item.getVoucherAmount(), null, controlAccount,
                                ShroffConstant.CR, item.getAnalysisCodeId(), item.getAnalysisCode(),
                                item.getVoucherItemNo() * 2 - 1);
                            creditItem.setVoucherId(autoJournalVoucher.getVoucherId());
                            creditItem.setRefName(controlAccount.getCtlAcName());
                            creditItem.setNature(item.getNature());
                            shrVcrItemRepository.save(creditItem);

                            debitItem = createAutoVoucherItem(item.getVoucherAmount(), caseAccountInfo, null,
                                ShroffConstant.DR, item.getAnalysisCodeId(), item.getAnalysisCode(),
                                item.getVoucherItemNo() * 2);
                            debitItem.setVoucherId(autoJournalVoucher.getVoucherId());
                            debitItem.setRefName(caseAccountInfo.getCaseAccountName());
                            debitItem.setNature(item.getNature());
                            shrVcrItemRepository.save(debitItem);
                            if (genLedgerNow) {
                                caseAccountInfo.setLiquidCashAmount(CommonUtils
                                    .subtract(caseAccountInfo.getLiquidCashAmount(), item.getVoucherAmount()));
                                BigDecimal liqCash
                                    = ShroffConstant.CTL_AC_NATURE_LIA.equals(controlAccount.getBalanceNature())
                                        ? CommonUtils.subtract(controlAccount.getLiquidCashAmount(),
                                            item.getVoucherAmount())
                                        : CommonUtils.add(controlAccount.getLiquidCashAmount(),
                                            item.getVoucherAmount());
                                controlAccount.setLiquidCashAmount(liqCash);
                                shrGeneralLedgerService.saveGeneralLedgerForBatchJob(item.getAnalysisCode(),
                                    autoJournalVoucher.getVoucherId(), null, item.getVoucherAmount(),
                                    caseAccountInfo.getLiquidCashAmount(), caseAccountInfo.getCaseAcId(), null,
                                    item.getNature(), autoJournalVoucher.getPoolRelated(),
                                    autoJournalVoucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                                shrGeneralLedgerService.saveGeneralLedgerForBatchJob(item.getAnalysisCode(),
                                    autoJournalVoucher.getVoucherId(), item.getVoucherAmount(), null,
                                    controlAccount.getLiquidCashAmount(), null, controlAccount.getCtlAcId(),
                                    item.getNature(), autoJournalVoucher.getPoolRelated(),
                                    autoJournalVoucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                            } else {
                                caseAccountInfo.setOnHoldAmountDr(
                                    CommonUtils.add(caseAccountInfo.getOnHoldAmountDr(), item.getVoucherAmount()));
                                controlAccount.setOnHoldAmountCr(
                                    CommonUtils.add(controlAccount.getOnHoldAmountCr(), item.getVoucherAmount()));
                            }
                            break;
                        default:
                            break;
                    }
                }
            }
        }

        switch (originVoucher.getVoucherType().getVoucherTypeCode()) {
            case ShroffConstant.VT_PAY:
                if (CQ.equals(originVoucher.getPaymentMethod())) {
                    ShrCheque shrCheque = chequeRepository.findOne(
                        ChequePredicate.findOutgoingChequeForPaymentVoucher(originVoucher.getVoucherId(), null));
                    if (shrCheque != null) {
                        shrCheque.setStatus(CoreConstant.CHEQUE_STATUS_CANCELLED);
                        chequeRepository.save(shrCheque);
                    }
                }
                break;
            default:
                break;
        }
        controlAccountRepository.save(controlAccount);
        caseAccountInfoRepository.save(caseAccountMap.values());
        log.info("createVoucherToReversePaymentAndReceiptVoucher() end");
    }

    private void createVoucherToReverseJournalVoucher(Voucher originVoucher, Voucher autoJournalVoucher,
        boolean genLedgerNow) {
        log.info("createVoucherToReverseJournalVoucher() start - originVoucher : " + originVoucher
            + " autoJournalVoucher : " + autoJournalVoucher);
        List<ShrVcrItem> shrVcrItems = originVoucher.getShrVcrItems();
        ShrVcrItem debitItem = null;
        ShrVcrItem creditItem = null;
        Map<Integer, CaseAccountInfo> caseAccountInfoMap = new HashMap<>();
        Map<Integer, ShrCtlAc> controlAccountMap = new HashMap<>();
        String caseName = "";
        String ctlAcName = "";
        for (ShrVcrItem item : shrVcrItems) {
            CaseAccountInfo caseAccountInfo = null;
            ShrCtlAc controlAccount = null;
            if (item.getCaseAccount() != null) {
                caseAccountInfo
                    = caseAccountInfoMap.getOrDefault(item.getCaseAccount().getCaseAcId(), item.getCaseAccount());
                caseName = caseAccountInfo.getCaseAccountName();
            }
            if (item.getControlAccount() != null) {
                controlAccount
                    = controlAccountMap.getOrDefault(item.getControlAccount().getCtlAcId(), item.getControlAccount());
                ctlAcName = controlAccount.getCtlAcName();
            }
            switch (item.getDebitCredit()) {
                case ShroffConstant.DR:
                    creditItem = createAutoVoucherItem(item.getVoucherAmount(), caseAccountInfo, controlAccount,
                        ShroffConstant.CR, item.getAnalysisCodeId(), item.getAnalysisCode(), item.getVoucherItemNo());
                    creditItem.setVoucherId(autoJournalVoucher.getVoucherId());
                    creditItem.setRefName(CommonUtils.isNotBlank(caseName) ? caseName : ctlAcName);
                    creditItem.setNature(item.getNature());
                    shrVcrItemRepository.save(creditItem);
                    if (caseAccountInfo != null) {
                        if (genLedgerNow) {
                            caseAccountInfo.setLiquidCashAmount(
                                CommonUtils.add(caseAccountInfo.getLiquidCashAmount(), item.getVoucherAmount()));
                            shrGeneralLedgerService.saveGeneralLedgerForBatchJob(item.getAnalysisCode(),
                                autoJournalVoucher.getVoucherId(), item.getVoucherAmount(), null,
                                caseAccountInfo.getLiquidCashAmount(), caseAccountInfo.getCaseAcId(), null,
                                item.getNature(), autoJournalVoucher.getPoolRelated(),
                                autoJournalVoucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                        } else {
                            caseAccountInfo.setOnHoldAmountCr(
                                CommonUtils.add(caseAccountInfo.getOnHoldAmountCr(), item.getVoucherAmount()));
                        }
                    } else {
                        if (genLedgerNow) {
                            BigDecimal liqCash
                                = ShroffConstant.CTL_AC_NATURE_LIA.equals(controlAccount.getBalanceNature())
                                    ? CommonUtils.subtract(controlAccount.getLiquidCashAmount(),
                                        item.getVoucherAmount())
                                    : CommonUtils.add(controlAccount.getLiquidCashAmount(), item.getVoucherAmount());
                            controlAccount.setLiquidCashAmount(liqCash);
                            shrGeneralLedgerService.saveGeneralLedgerForBatchJob(item.getAnalysisCode(),
                                autoJournalVoucher.getVoucherId(), item.getVoucherAmount(), null,
                                controlAccount.getLiquidCashAmount(), null, controlAccount.getCtlAcId(),
                                item.getNature(), autoJournalVoucher.getPoolRelated(),
                                autoJournalVoucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                        } else {
                            controlAccount.setOnHoldAmountCr(
                                CommonUtils.add(controlAccount.getOnHoldAmountCr(), item.getVoucherAmount()));
                        }
                    }
                    break;
                case ShroffConstant.CR:
                    debitItem = createAutoVoucherItem(item.getVoucherAmount(), caseAccountInfo, controlAccount,
                        ShroffConstant.DR, item.getAnalysisCodeId(), item.getAnalysisCode(), item.getVoucherItemNo());
                    debitItem.setVoucherId(autoJournalVoucher.getVoucherId());
                    debitItem.setRefName(CommonUtils.isNotBlank(caseName) ? caseName : ctlAcName);
                    debitItem.setNature(item.getNature());
                    shrVcrItemRepository.save(debitItem);
                    if (caseAccountInfo != null) {
                        if (genLedgerNow) {
                            caseAccountInfo.setLiquidCashAmount(
                                CommonUtils.subtract(caseAccountInfo.getLiquidCashAmount(), item.getVoucherAmount()));
                            shrGeneralLedgerService.saveGeneralLedgerForBatchJob(item.getAnalysisCode(),
                                autoJournalVoucher.getVoucherId(), null, item.getVoucherAmount(),
                                caseAccountInfo.getLiquidCashAmount(), caseAccountInfo.getCaseAcId(), null,
                                item.getNature(), autoJournalVoucher.getPoolRelated(),
                                autoJournalVoucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                        } else {
                            caseAccountInfo.setOnHoldAmountDr(
                                CommonUtils.add(caseAccountInfo.getOnHoldAmountDr(), item.getVoucherAmount()));
                        }
                    } else {
                        if (genLedgerNow) {
                            BigDecimal liqCash
                                = ShroffConstant.CTL_AC_NATURE_LIA.equals(controlAccount.getBalanceNature())
                                    ? CommonUtils.add(controlAccount.getLiquidCashAmount(), item.getVoucherAmount())
                                    : CommonUtils.subtract(controlAccount.getLiquidCashAmount(),
                                        item.getVoucherAmount());
                            controlAccount.setLiquidCashAmount(liqCash);
                            shrGeneralLedgerService.saveGeneralLedgerForBatchJob(item.getAnalysisCode(),
                                autoJournalVoucher.getVoucherId(), null, item.getVoucherAmount(),
                                controlAccount.getLiquidCashAmount(), null, controlAccount.getCtlAcId(),
                                item.getNature(), autoJournalVoucher.getPoolRelated(),
                                autoJournalVoucher.getCurrencyId(), null, item.getChequeNo(), item.getRefName());
                        } else {
                            controlAccount.setOnHoldAmountDr(
                                CommonUtils.add(controlAccount.getOnHoldAmountDr(), item.getVoucherAmount()));
                        }
                    }
                    break;
                default:
                    break;
            }
            if (caseAccountInfo != null) {
                caseAccountInfoMap.putIfAbsent(caseAccountInfo.getCaseAcId(), caseAccountInfo);
            }
            if (controlAccount != null) {
                controlAccountMap.putIfAbsent(controlAccount.getCtlAcId(), controlAccount);
            }
        }
        caseAccountInfoRepository.save(caseAccountInfoMap.values());
        controlAccountRepository.save(controlAccountMap.values());
        log.info("createVoucherToReverseJournalVoucher() end");
    }

    @Override
    @Transactional(readOnly = true)
    public BigDecimal findRealizationFee(String accountCode1) {
        log.info("createVoucherToReverseJournalVoucher() start accountCode1=" + accountCode1);
        if (accountCode1 == null)
            return null;
        Integer caseFeeTypeId = null;
        if (accountCode1.equals(ShroffConstant.VOUCHER_GROUP_CODE_BANKRUPTCY)) {
            caseFeeTypeId = 2;
        } else if (accountCode1.equals(ShroffConstant.VOUCHER_GROUP_CODE_LIQUIDATION)) {
            caseFeeTypeId = 16;
        }
        CaseFeeMain caseFeeMain = null;
        if (null != caseFeeTypeId) {
            caseFeeMain = caseFeeMainRepository
                .findOne(CaseFeeMainPredicate.findByCaseFeeTypeIdAndDate(caseFeeTypeId, new Date()));
        }
        BigDecimal realizationFee = null;
        if (null != caseFeeMain) {
            realizationFee = caseFeeMain.getFeeAmount();
        }
        log.info("createVoucherToReverseJournalVoucher() end  realizationFee=" + realizationFee);
        return realizationFee;
    }

    @Override
    @Transactional(readOnly = true)
    public Boolean validateExistsByVoucherNo(String voucherNo) {
        log.info("validateExistsByVoucherNo start - and voucherNo = " + voucherNo);
        Boolean result = false;
        Voucher voucher = voucherRepository.findVoucherByVoucherNo(voucherNo);
        if (voucher != null) {
            result = true;
        }
        log.info("validateExistsByVoucherNo end - return: " + result);
        return result;
    }

    @Override
    @Transactional(readOnly = true)
    public void createPaymentVoucher() {
        log.info("createPaymentVoucher start");
        List<Voucher> vouchers = voucherRepository.findVouchersByStatusAndCreatePaymentVoucherAndIsFinishCrePay(
            ShroffConstant.VCR_STATUS_POSTED, true, false);
        for (Voucher voucher : vouchers) {
            if (voucher.getIsFinishCrePay() != null && voucher.getIsFinishCrePay()) {
                continue;
            }

            Date workDays = HolidayUtils.getWorkDay(voucher.getLastUpdateDate(), 5);
            Date nowDate = new Date();
            if (nowDate.compareTo(workDays) > -1) {
                PaymentVoucherDetailDTO paymentVoucherDetailDTO = new PaymentVoucherDetailDTO();
                paymentVoucherDetailDTO
                    .setPaymentVoucherBasicInformation(getPaymentVoucherBasicInformationDTO(voucher));
                paymentVoucherDetailDTO.setPaymentVoucherItems(voucherService.getpaymentVoucherAccountItemDTOs(voucher));

                BigDecimal totalAmount = BigDecimal.ZERO;
                List<PaymentVoucherAccountItemDTO> paymentVoucherItems
                    = paymentVoucherDetailDTO.getPaymentVoucherItems();
                for (PaymentVoucherAccountItemDTO paymentVoucherAccountItemDTO : paymentVoucherItems) {
                    if(CoreConstant.STATUS_ACTIVE.equals(paymentVoucherAccountItemDTO.getStatus())){
                        totalAmount = totalAmount.add(paymentVoucherAccountItemDTO.getAmount());
                    }
                }
                paymentVoucherDetailDTO.getPaymentVoucherBasicInformation().setVoucherTotalAmount(totalAmount);
                
                VoucherDTO voucherDTO = DataUtils.copyProperties(voucher, VoucherDTO.class);
                paymentVoucherDetailDTO.setVoucherDTO(voucherDTO);

                Integer voucherId = voucherService.savePaymentVoucher(paymentVoucherDetailDTO);

                if (voucher.getCurrencyId() != null && voucher.getCurrencyId().equals(27)) {
                    BigDecimal usDollerToHkDoller = new BigDecimal("7.8");
                    totalAmount = totalAmount.multiply(usDollerToHkDoller);
                }
                if (totalAmount.compareTo(new BigDecimal("100000")) < 0) {
                    submitPay(voucherId);
                    approvePay(voucherId);
                    if(!isFSDDivision(voucher)){
                        verifyPay(voucherId, voucher.getControlAccount().getCtlAcId());
                        postedPay(voucherId);
                    }
                }
            }
        }

        log.info("createPaymentVoucher end");
    }

    private PaymentVoucherBasicInformationDTO getPaymentVoucherBasicInformationDTO(Voucher voucher) {
        log.info("getPaymentVoucherBasicInformationDTO start");
        PaymentVoucherBasicInformationDTO paymentVoucherBasicInformationDTO = new PaymentVoucherBasicInformationDTO();
        paymentVoucherBasicInformationDTO.setGroupCode(voucher.getGroupCode());
        //paymentVoucherBasicInformationDTO.setSubmissionDate(new Date());
        Calendar voucherDate = Calendar.getInstance();
        voucherDate.setTime(new Date());
        voucherDate.set(Calendar.SECOND, 0);
        voucherDate.set(Calendar.MINUTE, 0);
        voucherDate.set(Calendar.HOUR_OF_DAY, 0);
        voucherDate.set(Calendar.MILLISECOND, 0);
        paymentVoucherBasicInformationDTO.setVoucherDate(voucherDate.getTime());
        paymentVoucherBasicInformationDTO.setPaymentMethod(voucher.getReceiveMethod());
        paymentVoucherBasicInformationDTO.setFirstApproverId(voucher.getFirstApproverId());
        paymentVoucherBasicInformationDTO.setStatus(CoreConstant.COMMON_STATUS_DRAFT);
        paymentVoucherBasicInformationDTO.setVoucherSource(voucher.getVoucherNo());

        VoucherTypeDTO voucherTypeDTO = new VoucherTypeDTO();
        voucherTypeDTO.setVoucherTypeCode("PAY");
        paymentVoucherBasicInformationDTO.setVoucherType(voucherTypeDTO);

        paymentVoucherBasicInformationDTO.setVoucherTotalAmount(voucher.getVoucherTotalAmount());
        paymentVoucherBasicInformationDTO.setFileRef(voucher.getFileRef());
        paymentVoucherBasicInformationDTO.setVoucherNo("");
        //Post userInfo = getUserInfo();
        paymentVoucherBasicInformationDTO.setPreparerId(voucher.getPreparerId());
        paymentVoucherBasicInformationDTO.setCurrencyId(voucher.getCurrencyId());
        paymentVoucherBasicInformationDTO.setPayeeName(voucher.getPayeeName());
        paymentVoucherBasicInformationDTO.setRemark("Ad valorem fee(Create by System for Receipt Voucher No.: " + voucher.getVoucherNo() + ")");
        paymentVoucherBasicInformationDTO.setPoolRelated(voucher.getPoolRelated());
        paymentVoucherBasicInformationDTO.setSourceCode(voucher.getSourceCode());
        paymentVoucherBasicInformationDTO.setBankCode(voucher.getBankCode() == null ? "" : voucher.getBankCode());
        paymentVoucherBasicInformationDTO.setBranchCode(voucher.getBranchCode() == null ? "" : voucher.getBranchCode());
        paymentVoucherBasicInformationDTO
            .setAccountName(voucher.getAccountName() == null ? "" : voucher.getAccountName());
        paymentVoucherBasicInformationDTO
            .setAccountNumber(voucher.getAccountNumber() == null ? "" : voucher.getAccountNumber());
        paymentVoucherBasicInformationDTO.setIsHardCopy(voucher.getIsHardCopy());
        paymentVoucherBasicInformationDTO.setIsAutoPaymentVoucher(true);

        paymentVoucherBasicInformationDTO.setCreatedByPost("SYSTEM");
        paymentVoucherBasicInformationDTO.setCreatedByUser("system");
        paymentVoucherBasicInformationDTO.setLastUpdatedByPost("SYSTEM");
        paymentVoucherBasicInformationDTO.setLastUpdatedByUser("SYSTEM");
        paymentVoucherBasicInformationDTO.setTxnKeyRef(sysSequenceService.generateTxnKeyRefNo());
        log.info("getPaymentVoucherBasicInformationDTO() end");
        return paymentVoucherBasicInformationDTO;

    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public List<PaymentVoucherAccountItemDTO> getpaymentVoucherAccountItemDTOs(Voucher voucher) {
        log.info("getpaymentVoucherAccountItemDTOs start");
        List<PaymentVoucherAccountItemDTO> paymentVoucherAccountItemDTOs = new ArrayList<>();
        Integer voucherId = voucher.getVoucherId();
        Long keyRefNo = sysSequenceService.generateTxnKeyRefNo();
        List<ShrVcrItem> shrVcrItems = shrVcrItemRepository.findByVoucherId(voucherId);
        int voucherItemNo = 1;
        for (ShrVcrItem shrVcrItem : shrVcrItems) {
            if (shrVcrItem.getCreatePaymentVoucher()) {
                PaymentVoucherAccountItemDTO paymentVoucherAccountItemDTO = new PaymentVoucherAccountItemDTO();
                paymentVoucherAccountItemDTO.setVoucherItemNo(voucherItemNo);
                voucherItemNo++;
                AnalysisCode analysisCode = new AnalysisCode();
                if("L".equals(voucher.getGroupCode())){
                    analysisCode = analysisCodeRepository.findByAnalysisCode("255-044");
                }else if("B".equals(voucher.getGroupCode())){
                    analysisCode = analysisCodeRepository.findByAnalysisCode("255-020");
                }
                
                paymentVoucherAccountItemDTO.setAnalysisCode(analysisCode.getAnalysisCode());
                paymentVoucherAccountItemDTO.setAnalysisCodeId(analysisCode.getAnalysisCodeId());
                paymentVoucherAccountItemDTO.setNature(analysisCode.getFullName());
                paymentVoucherAccountItemDTO.setAmount(shrVcrItem.getVoucherAmount());
                paymentVoucherAccountItemDTO.setStatus(shrVcrItem.getStatus());

                Integer caseAcId = shrVcrItem.getCaseAccount().getCaseAcId();
                CaseAccountInfo caseAccountInfo = caseAccountInfoRepository.findOne(caseAcId);
                CaseAccountInfoDTO caseAccountInfoDTO = DataUtils.copyProperties(caseAccountInfo, CaseAccountInfoDTO.class);
                paymentVoucherAccountItemDTO.setAccount(caseAccountInfoDTO);

                paymentVoucherAccountItemDTO.setCaseTypeCodeValue(caseAccountInfoDTO.getCaseAccountCode1());
                paymentVoucherAccountItemDTO.setCaseYearValue(caseAccountInfoDTO.getCaseAccountYear().toString());
                paymentVoucherAccountItemDTO.setAccountTypeCodeValue(caseAccountInfoDTO.getCaseAccountCode2());

                paymentVoucherAccountItemDTO.setCreatedByPost("SYSTEM");
                paymentVoucherAccountItemDTO.setCreatedByUser("system");
                paymentVoucherAccountItemDTO.setLastUpdatedByPost("SYSTEM");
                paymentVoucherAccountItemDTO.setLastUpdatedByUser("SYSTEM");
                paymentVoucherAccountItemDTO.setTxnKeyRef(keyRefNo);

                String caseAccountSeq = caseAccountInfoDTO.getCaseAccountSeq().toString();
                int len = 5 - caseAccountSeq.length();
                StringBuilder stringBuilder = new StringBuilder();
                for (int i = 0; i < len; i++) {
                    stringBuilder.append(0);
                }
                stringBuilder.append(caseAccountSeq);
                paymentVoucherAccountItemDTO.setCaseNoValue(stringBuilder.toString());

                paymentVoucherAccountItemDTOs.add(paymentVoucherAccountItemDTO);
            }
        }
        log.info("getpaymentVoucherAccountItemDTOs() end");
        return paymentVoucherAccountItemDTOs;
    }

    private Map<SysAttachmentDTO, VoucherAttachmentDTO> getVoucherAttachmentDTOs(Integer receiptVoucherId) {
        log.info("getVoucherAttachmentDTOs start");
        List<ShrVcrAttachment> shrVcrAttachments
            = voucherAttachmentRepository.findVoucherAttachmentByVoucher(receiptVoucherId);
        Map<SysAttachmentDTO, VoucherAttachmentDTO> attachmentDTOMap = new HashMap<>();
        for (ShrVcrAttachment shrVcrAttachment : shrVcrAttachments) {
            SysAttachment sysAttachment
                = sysAttachmentRepository.findOne(shrVcrAttachment.getAttachment().getAttachmentId());
            SysAttachmentDTO sysAttachmentDTO = new SysAttachmentDTO();
            sysAttachmentDTO.setStatus(sysAttachment.getStatus());
            sysAttachmentDTO.setContent(sysAttachment.getContent());

            sysAttachmentDTO.setCreatedByPost("SYSTEM");
            sysAttachmentDTO.setCreatedByUser("system");
            sysAttachmentDTO.setLastUpdatedByPost("SYSTEM");
            sysAttachmentDTO.setLastUpdatedByUser("SYSTEM");
            sysAttachmentDTO.setTxnKeyRef(sysSequenceService.generateTxnKeyRefNo());

            VoucherAttachmentDTO voucherAttachmentDTO = new VoucherAttachmentDTO();
            voucherAttachmentDTO.setFileName(shrVcrAttachment.getFileName());
            voucherAttachmentDTO.setRemark(shrVcrAttachment.getRemark());
            voucherAttachmentDTO.setStatus(shrVcrAttachment.getStatus());
            // voucherAttachmentDTO.setAttachmentId(attachmentId);
            VoucherDTO voucherDTO = new VoucherDTO();
            // voucherDTO.setVoucherId(paymentVoucherId);
            voucherAttachmentDTO.setVoucher(voucherDTO);

            voucherAttachmentDTO.setCreatedByPost("SYSTEM");
            voucherAttachmentDTO.setCreatedByUser("system");
            voucherAttachmentDTO.setLastUpdatedByPost("SYSTEM");
            voucherAttachmentDTO.setLastUpdatedByUser("SYSTEM");
            voucherAttachmentDTO.setTxnKeyRef(sysSequenceService.generateTxnKeyRefNo());
            attachmentDTOMap.put(sysAttachmentDTO, voucherAttachmentDTO);
        }
        log.info("getVoucherAttachmentDTOs() end");
        return attachmentDTOMap;
    }

    private void submitPay(Integer voucherId) {
        log.info("submitPay start");
        PaymentVoucherDetailDTO paymentVoucherDetailDTO = getPaymentVoucherDetailDTO(voucherId);
        try {
            paymentVoucherDetailDTO.getPaymentVoucherBasicInformation().setSubmissionDate(new Date());
            voucherService.submitPaymentVoucher(paymentVoucherDetailDTO);
        } catch (Exception be) {
            log.info(be.getMessage(), be);
        }
        log.info("submitPay() end");
    }

    private void approvePay(Integer voucherId) {
        log.info("approvePay start");
        PaymentVoucherDetailDTO paymentVoucherDetailDTO = getPaymentVoucherDetailDTO(voucherId);
        try {
            voucherService.approvePaymentVoucher(paymentVoucherDetailDTO);
        } catch (Exception be) {
            log.info(be.getMessage(), be);
        }
        log.info("approvePay() end");
    }

    private void verifyPay(Integer AppVoucherId, Integer ctlAcId) {
        log.info("verifyPay start");
        PaymentVoucherDetailDTO paymentVoucherDetailDTO = getPaymentVoucherDetailDTO(AppVoucherId);
        if (ctlAcId != null) {
            ControlAccountDTO controlAccountDetail = controlAccountService.getControlAccountDetail(ctlAcId);
            paymentVoucherDetailDTO.getPaymentVoucherBasicInformation().setControlAccount(controlAccountDetail);
        }
        try {
            voucherService.verifyPaymentVoucher(paymentVoucherDetailDTO);
        } catch (Exception be) {
            log.info(be.getMessage(), be);
        }
        log.info("verifyPay() end");
    }

    private void postedPay(Integer verifyVoucherID) {
        log.info("postedPay start");
        PaymentVoucherDetailDTO paymentVoucherDetailDTO = getPaymentVoucherDetailDTO(verifyVoucherID);
        VoucherDTO voucherDTO
            = DataUtils.copyProperties(paymentVoucherDetailDTO.getPaymentVoucherBasicInformation(), VoucherDTO.class);
        VoucherDetailDTO voucherDetailDTO = new VoucherDetailDTO();
        voucherDetailDTO.setVoucherDTO(voucherDTO);
        try {
            voucherService.handleVoucherBatchJob(voucherDetailDTO);
        } catch (Exception be) {
            log.info(be.getMessage(), be);
        }
        log.info("postedPay() end");
    }

    private Post getUserInfo() {
        return postService.findByPostTitle("SYSTEM");
    }

    private PaymentVoucherDetailDTO getPaymentVoucherDetailDTO(Integer voucherId) {
        entityManager.clear();
        return findPaymentVoucher(voucherId);
    }
    
    private Boolean isFSDDivision(Voucher voucher){
        log.info("isFSDDivision start voucherNo=" + voucher.getVoucherNo());
        PostDTO firstApprovePost = null;
        if(voucher.getFirstApproverId() != null){
            firstApprovePost = postService.findOne(voucher.getFirstApproverId());
        }

        String firstApproveDivision = null;
        if(firstApprovePost != null && firstApprovePost.getDivision() != null && firstApprovePost.getDivision().getDivisionName() != null){
            String firstApproveDivisionName = firstApprovePost.getDivision().getDivisionName();
            firstApproveDivision = firstApproveDivisionName.split("-")[0].trim();
        }
        Boolean result = CoreConstant.DIVISION_FSD.equals(firstApproveDivision);
        log.info("isFSDDivision() end division=" + firstApproveDivision + " result=" + result);
        return result;
    }

}
